mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 02:46:29 +01:00 
			
		
		
		
	 d5f16d6305
			
		
	
	
	d5f16d6305
	
	
	
		
			
			That PR uncovered countless issues on `my_snprintf` uses. This commit backports a squashed subset of their fixes (excludes #3485).
		
			
				
	
	
		
			9364 lines
		
	
	
	
		
			307 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			9364 lines
		
	
	
	
		
			307 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Copyright (C) 2007 MySQL AB & Sanja Belkin. 2010 Monty Program Ab.
 | |
|    Copyright (c) 2020, MariaDB Corporation.
 | |
| 
 | |
|    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; version 2 of the License.
 | |
| 
 | |
|    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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
 | |
| 
 | |
| #include "maria_def.h"
 | |
| #include "trnman.h"
 | |
| #include "ma_blockrec.h" /* for some constants and in-write hooks */
 | |
| #include "ma_key_recover.h" /* For some in-write hooks */
 | |
| #include "ma_checkpoint.h"
 | |
| #include "ma_servicethread.h"
 | |
| #include "ma_recovery.h"
 | |
| #include "ma_loghandler_lsn.h"
 | |
| #include "ma_recovery_util.h"
 | |
| 
 | |
| /*
 | |
|   On Windows, neither my_open() nor mysql_file_sync() work for directories.
 | |
|   Also there is no need to flush filesystem changes ,i.e to sync()
 | |
|   directories.
 | |
| */
 | |
| #ifdef _WIN32
 | |
| #define sync_dir(A,B) 0
 | |
| #else
 | |
| #define sync_dir(A,B) mysql_file_sync(A,B)
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|    @file
 | |
|    @brief Module which writes and reads to a transaction log
 | |
| */
 | |
| 
 | |
| /* 0xFF can never be valid first byte of a chunk */
 | |
| #define TRANSLOG_FILLER 0xFF
 | |
| 
 | |
| /* number of opened log files in the pagecache (should be at least 2) */
 | |
| #define OPENED_FILES_NUM 3
 | |
| #define CACHED_FILES_NUM 5
 | |
| #define CACHED_FILES_NUM_DIRECT_SEARCH_LIMIT 7
 | |
| #if CACHED_FILES_NUM > CACHED_FILES_NUM_DIRECT_SEARCH_LIMIT
 | |
| #include <hash.h>
 | |
| #include <m_ctype.h>
 | |
| #endif
 | |
| 
 | |
| /** @brief protects checkpoint_in_progress */
 | |
| static mysql_mutex_t LOCK_soft_sync;
 | |
| /** @brief for killing the background checkpoint thread */
 | |
| static mysql_cond_t  COND_soft_sync;
 | |
| /** @brief control structure for checkpoint background thread */
 | |
| static MA_SERVICE_THREAD_CONTROL soft_sync_control=
 | |
|   {0, FALSE, FALSE, &LOCK_soft_sync, &COND_soft_sync};
 | |
| 
 | |
| uint log_purge_disabled= 0;
 | |
| 
 | |
| 
 | |
| /* transaction log file descriptor */
 | |
| typedef struct st_translog_file
 | |
| {
 | |
|   uint32 number;
 | |
|   PAGECACHE_FILE handler;
 | |
|   my_bool was_recovered;
 | |
|   my_bool is_sync;
 | |
| } TRANSLOG_FILE;
 | |
| 
 | |
| /* records buffer size (should be TRANSLOG_PAGE_SIZE * n) */
 | |
| #define TRANSLOG_WRITE_BUFFER (1024*1024)
 | |
| /*
 | |
|   pagecache_read/write/inject() use bmove512() on their buffers so those must
 | |
|   be long-aligned, which we guarantee by using the type below:
 | |
| */
 | |
| typedef union
 | |
| {
 | |
|   ulonglong dummy;
 | |
|   uchar buffer[TRANSLOG_PAGE_SIZE];
 | |
| } TRANSLOG_PAGE_SIZE_BUFF;
 | |
| 
 | |
| #define MAX_TRUNSLOG_USED_BUFFERS 3
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|  struct st_translog_buffer *buff[MAX_TRUNSLOG_USED_BUFFERS];
 | |
|  uint8 wrt_ptr;
 | |
|  uint8 unlck_ptr;
 | |
| } TRUNSLOG_USED_BUFFERS;
 | |
| 
 | |
| static void
 | |
| used_buffs_init(TRUNSLOG_USED_BUFFERS *buffs)
 | |
| {
 | |
|   buffs->unlck_ptr= buffs->wrt_ptr= 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| used_buffs_add(TRUNSLOG_USED_BUFFERS *buffs,
 | |
|                 struct st_translog_buffer *buff);
 | |
| 
 | |
| static void
 | |
| used_buffs_register_unlock(TRUNSLOG_USED_BUFFERS *buffs,
 | |
|                            struct st_translog_buffer *buff);
 | |
| 
 | |
| static void
 | |
| used_buffs_urgent_unlock(TRUNSLOG_USED_BUFFERS *buffs);
 | |
| 
 | |
| /* min chunk length */
 | |
| #define TRANSLOG_MIN_CHUNK 3
 | |
| /*
 | |
|   Number of buffers used by loghandler
 | |
| 
 | |
|   Should be at least 4, because one thread can block up to 2 buffers in
 | |
|   normal circumstances (less then half of one and full other, or just
 | |
|   switched one and other), But if we met end of the file in the middle and
 | |
|   have to switch buffer it will be 3.  + 1 buffer for flushing/writing.
 | |
|   We have a bigger number here for higher concurrency and to make division
 | |
|   faster.
 | |
| 
 | |
|   The number should be power of 2 to be fast.
 | |
| */
 | |
| #define TRANSLOG_BUFFERS_NO 8
 | |
| /* number of bytes (+ header) which can be unused on first page in sequence */
 | |
| #define TRANSLOG_MINCHUNK_CONTENT 1
 | |
| /* version of log file */
 | |
| #define TRANSLOG_VERSION_ID 10000               /* 1.00.00 */
 | |
| 
 | |
| #define TRANSLOG_PAGE_FLAGS 6 /* transaction log page flags offset */
 | |
| 
 | |
| /* Maximum length of compressed LSNs (the worst case of whole LSN storing) */
 | |
| #define COMPRESSED_LSN_MAX_STORE_SIZE (2 + LSN_STORE_SIZE)
 | |
| #define MAX_NUMBER_OF_LSNS_PER_RECORD 2
 | |
| 
 | |
| 
 | |
| /* max lsn calculation for buffer */
 | |
| #define BUFFER_MAX_LSN(B)  \
 | |
|   ((B)->last_lsn == LSN_IMPOSSIBLE ? (B)->prev_last_lsn : (B)->last_lsn)
 | |
| 
 | |
| /* log write buffer descriptor */
 | |
| struct st_translog_buffer
 | |
| {
 | |
|   /*
 | |
|     Cache for current log. Comes first to be aligned for bmove512() in
 | |
|     pagecache_inject()
 | |
|   */
 | |
|   uchar buffer[TRANSLOG_WRITE_BUFFER];
 | |
|   /*
 | |
|     Maximum LSN of records which ends in this buffer (or IMPOSSIBLE_LSN
 | |
|     if no LSNs ends here)
 | |
|   */
 | |
|   LSN last_lsn;
 | |
|   /* last_lsn of previous buffer or IMPOSSIBLE_LSN if it is very first one */
 | |
|   LSN prev_last_lsn;
 | |
|   /* This buffer offset in the file */
 | |
|   TRANSLOG_ADDRESS offset;
 | |
|   /*
 | |
|     Next buffer offset in the file (it is not always offset + size,
 | |
|     in case of flush by LSN it can be offset + size - TRANSLOG_PAGE_SIZE)
 | |
|   */
 | |
|   TRANSLOG_ADDRESS next_buffer_offset;
 | |
|   /* Previous buffer offset to detect it flush finish */
 | |
|   TRANSLOG_ADDRESS prev_buffer_offset;
 | |
|   /*
 | |
|     If the buffer was forced to close it save value of its horizon
 | |
|     otherwise LSN_IMPOSSIBLE
 | |
|   */
 | |
|   TRANSLOG_ADDRESS pre_force_close_horizon;
 | |
|   /*
 | |
|      How much is written (or will be written when copy_to_buffer_in_progress
 | |
|      become 0) to this buffer
 | |
|   */
 | |
|   translog_size_t size;
 | |
|   /*
 | |
|      When moving from one log buffer to another, we write the last of the
 | |
|      previous buffer to file and then move to start using the new log
 | |
|      buffer.  In the case of a part filed last page, this page is not moved
 | |
|      to the start of the new buffer but instead we set the 'skip_data'
 | |
|      variable to tell us how much data at the beginning of the buffer is not
 | |
|      relevant.
 | |
|   */
 | |
|   uint skipped_data;
 | |
|   /* File handler for this buffer */
 | |
|   TRANSLOG_FILE *file;
 | |
|   /* Threads which are waiting for buffer filling/freeing */
 | |
|   mysql_cond_t waiting_filling_buffer;
 | |
|   /*
 | |
|     Number of records which are in copy progress.
 | |
| 
 | |
|     Controlled via translog_buffer_increase_writers() and
 | |
|     translog_buffer_decrease_writers().
 | |
| 
 | |
|     1 Simple case: translog_force_current_buffer_to_finish both called in
 | |
|     the same procedure.
 | |
| 
 | |
|     2 Simple case: translog_write_variable_record_1group:
 | |
|     translog_advance_pointer() increase writer of the buffer and
 | |
|     translog_buffer_decrease_writers() decrease it.
 | |
| 
 | |
|     Usual case:
 | |
|      1) translog_advance_pointer (i.e. reserve place for future writing)
 | |
|      increase writers for all buffers where place reserved.
 | |
|        Simpliest case: just all space reserved in one buffer
 | |
|        complex case: end of the first buffer, all second buffer, beginning
 | |
|        of the third buffer.
 | |
|      2) When we finish with writing translog_chaser_page_next() will be
 | |
|      called and unlock the buffer by decreasing number of writers.
 | |
|   */
 | |
|   uint copy_to_buffer_in_progress;
 | |
|   /* list of waiting buffer ready threads */
 | |
|   struct st_my_thread_var *waiting_flush;
 | |
|   /*
 | |
|     If true then previous buffer overlap with this one (due to flush of
 | |
|     loghandler, the last page of that buffer is the same as the first page
 | |
|     of this buffer) and have to be written first (because contain old
 | |
|     content of page which present in both buffers)
 | |
|   */
 | |
|   my_bool overlay;
 | |
|   uint buffer_no;
 | |
|   /*
 | |
|     Lock for the buffer.
 | |
| 
 | |
|     Current buffer also lock the whole handler (if one want lock the handler
 | |
|     one should lock the current buffer).
 | |
| 
 | |
|     Buffers are locked only in one direction (with overflow and beginning
 | |
|     from the first buffer). If we keep lock on buffer N we can lock only
 | |
|     buffer N+1 (never N-1).
 | |
| 
 | |
|     One thread do not lock more then 2 buffer in a time, so to make dead
 | |
|     lock it should be N thread (where N equal number of buffers) takes one
 | |
|     buffer and try to lock next. But it is impossible because there is only
 | |
|     2 cases when thread take 2 buffers: 1) one thread finishes current
 | |
|     buffer (where horizon is) and start next (to which horizon moves).  2)
 | |
|     flush start from buffer after current (oldest) and go till the current
 | |
|     crabbing by buffer sequence. And there is  only one flush in a moment
 | |
|     (they are serialised).
 | |
| 
 | |
|    Because of above and number of buffers equal 5 we can't get dead lock (it is
 | |
|    impossible to get all 5 buffers locked simultaneously).
 | |
|   */
 | |
|   mysql_mutex_t mutex;
 | |
|   /*
 | |
|     Some thread is going to close the buffer and it should be
 | |
|     done only by that thread
 | |
|   */
 | |
|   my_bool is_closing_buffer;
 | |
|   /*
 | |
|     Version of the buffer increases every time buffer the buffer flushed.
 | |
|     With file and offset it allow detect buffer changes
 | |
|   */
 | |
|   uint8 ver;
 | |
| 
 | |
|   /*
 | |
|     When previous buffer sent to disk it set its address here to allow
 | |
|     to detect when it is done
 | |
|     (we have to keep it in this buffer to lock buffers only in one direction).
 | |
|   */
 | |
|   TRANSLOG_ADDRESS prev_sent_to_disk;
 | |
|   mysql_cond_t prev_sent_to_disk_cond;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct st_buffer_cursor
 | |
| {
 | |
|   TRUNSLOG_USED_BUFFERS buffs;
 | |
|   /* pointer into the buffer */
 | |
|   uchar *ptr;
 | |
|   /* current buffer */
 | |
|   struct st_translog_buffer *buffer;
 | |
|   /* How many bytes we wrote on the current page */
 | |
|   uint16 current_page_fill;
 | |
|   /*
 | |
|     How many times we write the page on the disk during flushing process
 | |
|     (for sector protection).
 | |
|   */
 | |
|   uint16 write_counter;
 | |
|   /* previous write offset */
 | |
|   uint16 previous_offset;
 | |
|   /* Number of current buffer */
 | |
|   uint8 buffer_no;
 | |
|   /*
 | |
|     True if it is just filling buffer after advancing the pointer to
 | |
|     the horizon.
 | |
|   */
 | |
|   my_bool chaser;
 | |
|   /*
 | |
|     Is current page of the cursor already finished (sector protection
 | |
|     should be applied if it is needed)
 | |
|   */
 | |
|   my_bool protected;
 | |
| };
 | |
| 
 | |
| 
 | |
| typedef uint8 dirty_buffer_mask_t;
 | |
| 
 | |
| struct st_translog_descriptor
 | |
| {
 | |
|   /* *** Parameters of the log handler *** */
 | |
| 
 | |
|   /* Page cache for the log reads */
 | |
|   PAGECACHE *pagecache;
 | |
|   uint flags;
 | |
|   /* File open flags */
 | |
|   uint open_flags;
 | |
|   /* max size of one log size (for new logs creation) */
 | |
|   uint32 log_file_max_size;
 | |
|   uint32 server_version;
 | |
|   /* server ID (used for replication) */
 | |
|   uint32 server_id;
 | |
|   /* Loghandler's buffer capacity in case of chunk 2 filling */
 | |
|   uint32 buffer_capacity_chunk_2;
 | |
|   /*
 | |
|     Half of the buffer capacity in case of chunk 2 filling,
 | |
|     used to decide will we write a record in one group or many.
 | |
|     It is written to the variable just to avoid devision every
 | |
|     time we need it.
 | |
|   */
 | |
|   uint32 half_buffer_capacity_chunk_2;
 | |
|   /* Page overhead calculated by flags (whether CRC is enabled, etc) */
 | |
|   uint16 page_overhead;
 | |
|   /*
 | |
|     Page capacity ("useful load") calculated by flags
 | |
|     (TRANSLOG_PAGE_SIZE - page_overhead-1)
 | |
|   */
 | |
|   uint16 page_capacity_chunk_2;
 | |
|   /* Path to the directory where we store log store files */
 | |
|   char directory[FN_REFLEN];
 | |
| 
 | |
|   /* *** Current state of the log handler *** */
 | |
|   /* list of opened files */
 | |
|   DYNAMIC_ARRAY open_files;
 | |
|   /* min/max number of file in the array */
 | |
|   uint32 max_file, min_file;
 | |
|   /* the opened files list guard */
 | |
|   mysql_rwlock_t open_files_lock;
 | |
| 
 | |
|   /*
 | |
|     File descriptor of the directory where we store log files for syncing
 | |
|     it.
 | |
|   */
 | |
|   File directory_fd;
 | |
|   /* buffers for log writing */
 | |
|   struct st_translog_buffer buffers[TRANSLOG_BUFFERS_NO];
 | |
|   /* Mask where 1 in position N mean that buffer N is not flushed */
 | |
|   dirty_buffer_mask_t dirty_buffer_mask;
 | |
|   /* The above variable protection */
 | |
|   mysql_mutex_t dirty_buffer_mask_lock;
 | |
|   /*
 | |
|      horizon - visible end of the log (here is absolute end of the log:
 | |
|      position where next chunk can start
 | |
|   */
 | |
|   TRANSLOG_ADDRESS horizon;
 | |
|   /* horizon buffer cursor */
 | |
|   struct st_buffer_cursor bc;
 | |
|   /* maximum LSN of the current (not finished) file */
 | |
|   LSN max_lsn;
 | |
| 
 | |
|   /*
 | |
|     Last flushed LSN (protected by log_flush_lock).
 | |
|     Pointers in the log ordered like this:
 | |
|     last_lsn_checked <= flushed <= sent_to_disk <= in_buffers_only <=
 | |
|     max_lsn <= horizon
 | |
|   */
 | |
|   LSN flushed;
 | |
|   /* Last LSN sent to the disk (but maybe not written yet) */
 | |
|   LSN sent_to_disk;
 | |
|   /* Horizon from which log started after initialization */
 | |
|   TRANSLOG_ADDRESS log_start;
 | |
|   TRANSLOG_ADDRESS previous_flush_horizon;
 | |
|   /* All what is after this address is not sent to disk yet */
 | |
|   TRANSLOG_ADDRESS in_buffers_only;
 | |
|   /* protection of sent_to_disk and in_buffers_only */
 | |
|   mysql_mutex_t sent_to_disk_lock;
 | |
|   /*
 | |
|     Protect flushed (see above) and for flush serialization (will
 | |
|     be removed in v1.5
 | |
|   */
 | |
|   mysql_mutex_t log_flush_lock;
 | |
|   mysql_cond_t log_flush_cond;
 | |
|   mysql_cond_t new_goal_cond;
 | |
| 
 | |
|   /* Protects changing of headers of finished files (max_lsn) */
 | |
|   mysql_mutex_t file_header_lock;
 | |
| 
 | |
|   /*
 | |
|     Sorted array (with protection) of files where we started writing process
 | |
|     and so we can't give last LSN yet
 | |
|   */
 | |
|   mysql_mutex_t unfinished_files_lock;
 | |
|   DYNAMIC_ARRAY unfinished_files;
 | |
| 
 | |
|   /*
 | |
|     minimum number of still need file calculeted during last
 | |
|     translog_purge call
 | |
|   */
 | |
|   uint32 min_need_file;
 | |
|   /* Purger data: minimum file in the log (or 0 if unknown) */
 | |
|   uint32 min_file_number;
 | |
|   /* Protect purger from many calls and it's data */
 | |
|   mysql_mutex_t purger_lock;
 | |
|   /* last low water mark checked */
 | |
|   LSN last_lsn_checked;
 | |
|   /**
 | |
|     Must be set to 0 under loghandler lock every time a new LSN
 | |
|     is generated.
 | |
|   */
 | |
|   my_bool is_everything_flushed;
 | |
|   /* True when flush pass is in progress */
 | |
|   my_bool flush_in_progress;
 | |
|   /* The flush number (used to distinguish two flushes goes one by one) */
 | |
|   volatile int flush_no;
 | |
|   /* Next flush pass variables */
 | |
|   TRANSLOG_ADDRESS next_pass_max_lsn;
 | |
|   pthread_t max_lsn_requester;
 | |
| };
 | |
| 
 | |
| static struct st_translog_descriptor log_descriptor;
 | |
| 
 | |
| ulong log_purge_type= TRANSLOG_PURGE_IMMIDIATE;
 | |
| ulong log_file_size= TRANSLOG_FILE_SIZE;
 | |
| /* sync() of log files directory mode */
 | |
| ulong sync_log_dir= TRANSLOG_SYNC_DIR_NEWFILE;
 | |
| ulong maria_group_commit= TRANSLOG_GCOMMIT_NONE;
 | |
| ulong maria_group_commit_interval= 0;
 | |
| 
 | |
| /* Marker for end of log */
 | |
| static uchar end_of_log= 0;
 | |
| #define END_OF_LOG &end_of_log
 | |
| /**
 | |
|   Switch for "soft" sync (no real sync() but periodical sync by service
 | |
|   thread)
 | |
| */
 | |
| static volatile my_bool soft_sync= FALSE;
 | |
| /**
 | |
|   Switch for "hard" group commit mode
 | |
| */
 | |
| static volatile my_bool hard_group_commit= FALSE;
 | |
| /**
 | |
|   File numbers interval which have to be sync()
 | |
| */
 | |
| static uint32 soft_sync_min= 0;
 | |
| static uint32 soft_sync_max= 0;
 | |
| static uint32 soft_need_sync= 1;
 | |
| /**
 | |
|   stores interval in microseconds
 | |
| */
 | |
| static uint32 group_commit_wait= 0;
 | |
| 
 | |
| enum enum_translog_status translog_status= TRANSLOG_UNINITED;
 | |
| ulonglong translog_syncs= 0; /* Number of sync()s */
 | |
| 
 | |
| /* time of last flush */
 | |
| static ulonglong flush_start= 0;
 | |
| 
 | |
| /* chunk types */
 | |
| #define TRANSLOG_CHUNK_LSN   0x00      /* 0 chunk refer as LSN (head or tail */
 | |
| #define TRANSLOG_CHUNK_FIXED (1 << 6)  /* 1 (pseudo)fixed record (also LSN) */
 | |
| #define TRANSLOG_CHUNK_NOHDR (2 << 6)  /* 2 no head chunk (till page end) */
 | |
| #define TRANSLOG_CHUNK_LNGTH (3 << 6)  /* 3 chunk with chunk length */
 | |
| #define TRANSLOG_CHUNK_TYPE  (3 << 6)  /* Mask to get chunk type */
 | |
| #define TRANSLOG_REC_TYPE    0x3F      /* Mask to get record type */
 | |
| #define TRANSLOG_CHUNK_0_CONT 0x3F     /* the type to mark chunk 0 continue */
 | |
| 
 | |
| /* compressed (relative) LSN constants */
 | |
| #define TRANSLOG_CLSN_LEN_BITS 0xC0    /* Mask to get compressed LSN length */
 | |
| 
 | |
| 
 | |
| /* an array that maps id of a MARIA_SHARE to this MARIA_SHARE */
 | |
| static MARIA_SHARE **id_to_share= NULL;
 | |
| 
 | |
| static my_bool translog_page_validator(int res, PAGECACHE_IO_HOOK_ARGS *args);
 | |
| 
 | |
| static my_bool translog_get_next_chunk(TRANSLOG_SCANNER_DATA *scanner);
 | |
| static uint32 translog_first_file(TRANSLOG_ADDRESS horizon, int is_protected);
 | |
| LSN translog_next_LSN(TRANSLOG_ADDRESS addr, TRANSLOG_ADDRESS horizon);
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Initialize log_record_type_descriptors
 | |
| */
 | |
| 
 | |
| LOG_DESC log_record_type_descriptor[LOGREC_NUMBER_OF_TYPES];
 | |
| 
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
| 
 | |
| #define translog_buffer_lock_assert_owner(B) \
 | |
|   mysql_mutex_assert_owner(&(B)->mutex)
 | |
| #define translog_lock_assert_owner() \
 | |
|   mysql_mutex_assert_owner(&log_descriptor.bc.buffer->mutex)
 | |
| void translog_lock_handler_assert_owner()
 | |
| {
 | |
|   translog_lock_assert_owner();
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief check the description table validity
 | |
| 
 | |
|   @param num             how many records should be filled
 | |
| */
 | |
| 
 | |
| static uint max_allowed_translog_type= 0;
 | |
| 
 | |
| void check_translog_description_table(int num)
 | |
| {
 | |
|   int i;
 | |
|   DBUG_ENTER("check_translog_description_table");
 | |
|   DBUG_PRINT("enter", ("last record: %d", num));
 | |
|   DBUG_ASSERT(num > 0);
 | |
|   /* last is reserved for extending the table */
 | |
|   DBUG_ASSERT(num < LOGREC_NUMBER_OF_TYPES - 1);
 | |
|   DBUG_ASSERT(log_record_type_descriptor[0].rclass == LOGRECTYPE_NOT_ALLOWED);
 | |
|   max_allowed_translog_type= num;
 | |
| 
 | |
|   for (i= 0; i <= num; i++)
 | |
|   {
 | |
|     DBUG_PRINT("info",
 | |
|                ("record type: %d  class: %d  fixed: %u  header: %u  LSNs: %u  "
 | |
|                 "name: %s",
 | |
|                 i, log_record_type_descriptor[i].rclass,
 | |
|                 (uint)log_record_type_descriptor[i].fixed_length,
 | |
|                 (uint)log_record_type_descriptor[i].read_header_len,
 | |
|                 (uint)log_record_type_descriptor[i].compressed_LSN,
 | |
|                 log_record_type_descriptor[i].name));
 | |
|     switch (log_record_type_descriptor[i].rclass) {
 | |
|     case LOGRECTYPE_NOT_ALLOWED:
 | |
|       DBUG_ASSERT(i == 0);
 | |
|       break;
 | |
|     case LOGRECTYPE_VARIABLE_LENGTH:
 | |
|       DBUG_ASSERT(log_record_type_descriptor[i].fixed_length == 0);
 | |
|       DBUG_ASSERT((log_record_type_descriptor[i].compressed_LSN == 0) ||
 | |
|                   ((log_record_type_descriptor[i].compressed_LSN == 1) &&
 | |
|                    (log_record_type_descriptor[i].read_header_len >=
 | |
|                     LSN_STORE_SIZE)) ||
 | |
|                   ((log_record_type_descriptor[i].compressed_LSN == 2) &&
 | |
|                    (log_record_type_descriptor[i].read_header_len >=
 | |
|                     LSN_STORE_SIZE * 2)));
 | |
|       break;
 | |
|     case LOGRECTYPE_PSEUDOFIXEDLENGTH:
 | |
|       DBUG_ASSERT(log_record_type_descriptor[i].fixed_length ==
 | |
|                   log_record_type_descriptor[i].read_header_len);
 | |
|       DBUG_ASSERT(log_record_type_descriptor[i].compressed_LSN > 0);
 | |
|       DBUG_ASSERT(log_record_type_descriptor[i].compressed_LSN <= 2);
 | |
|       break;
 | |
|     case LOGRECTYPE_FIXEDLENGTH:
 | |
|       DBUG_ASSERT(log_record_type_descriptor[i].fixed_length ==
 | |
|                   log_record_type_descriptor[i].read_header_len);
 | |
|       DBUG_ASSERT(log_record_type_descriptor[i].compressed_LSN == 0);
 | |
|       break;
 | |
|     default:
 | |
|       DBUG_ASSERT(0);
 | |
|     }
 | |
|   }
 | |
|   for (i= num + 1; i < LOGREC_NUMBER_OF_TYPES; i++)
 | |
|   {
 | |
|     DBUG_ASSERT(log_record_type_descriptor[i].rclass ==
 | |
|                 LOGRECTYPE_NOT_ALLOWED);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| #else
 | |
| #define translog_buffer_lock_assert_owner(B) {}
 | |
| #define translog_lock_assert_owner() {}
 | |
| #endif
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_RESERVED_FOR_CHUNKS23=
 | |
| {LOGRECTYPE_NOT_ALLOWED, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "reserved", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL };
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_INSERT_ROW_HEAD=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL,
 | |
|  write_hook_for_redo, NULL, 0,
 | |
|  "redo_insert_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_INSERT_ROW_TAIL=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL,
 | |
|  write_hook_for_redo, NULL, 0,
 | |
|  "redo_insert_row_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_NEW_ROW_HEAD=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL,
 | |
|  write_hook_for_redo, NULL, 0,
 | |
|  "redo_new_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_NEW_ROW_TAIL=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE, NULL,
 | |
|  write_hook_for_redo, NULL, 0,
 | |
|  "redo_new_row_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_INSERT_ROW_BLOBS=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, FILEID_STORE_SIZE, NULL,
 | |
|  write_hook_for_redo, NULL, 0,
 | |
|  "redo_insert_row_blobs", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_PURGE_ROW_HEAD=
 | |
| {LOGRECTYPE_FIXEDLENGTH,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_purge_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_PURGE_ROW_TAIL=
 | |
| {LOGRECTYPE_FIXEDLENGTH,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_purge_row_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_FREE_BLOCKS=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  FILEID_STORE_SIZE + PAGERANGE_STORE_SIZE,
 | |
|  NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_free_blocks", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_FREE_HEAD_OR_TAIL=
 | |
| {LOGRECTYPE_FIXEDLENGTH,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE,
 | |
|  NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_free_head_or_tail", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| /* not yet used; for when we have versioning */
 | |
| static LOG_DESC INIT_LOGREC_REDO_DELETE_ROW=
 | |
| {LOGRECTYPE_FIXEDLENGTH, 16, 16, NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_delete_row", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| /** @todo RECOVERY BUG unused, remove? */
 | |
| static LOG_DESC INIT_LOGREC_REDO_UPDATE_ROW_HEAD=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 9, NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_update_row_head", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_INDEX=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 9, NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_index", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_INDEX_NEW_PAGE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE + 1,
 | |
|  NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_index_new_page", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_INDEX_FREE_PAGE=
 | |
| {LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2,
 | |
|  NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_index_free_page", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_UNDELETE_ROW=
 | |
| {LOGRECTYPE_FIXEDLENGTH, 16, 16, NULL, write_hook_for_redo, NULL, 0,
 | |
|  "redo_undelete_row", LOGREC_NOT_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_CLR_END=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, LSN_STORE_SIZE + FILEID_STORE_SIZE +
 | |
|  CLR_TYPE_STORE_SIZE, NULL, write_hook_for_clr_end, NULL, 1,
 | |
|  "clr_end", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_PURGE_END=
 | |
| {LOGRECTYPE_PSEUDOFIXEDLENGTH, 5, 5, NULL, NULL, NULL, 1,
 | |
|  "purge_end", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_ROW_INSERT=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_row_insert, NULL, 1,
 | |
|  "undo_row_insert", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_ROW_DELETE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_row_delete, NULL, 1,
 | |
|  "undo_row_delete", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_ROW_UPDATE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + PAGE_STORE_SIZE + DIRPOS_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_row_update, NULL, 1,
 | |
|  "undo_row_update", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_KEY_INSERT=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_key_insert, NULL, 1,
 | |
|  "undo_key_insert", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| /* This will never be in the log, only in the clr */
 | |
| static LOG_DESC INIT_LOGREC_UNDO_KEY_INSERT_WITH_ROOT=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE + PAGE_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_key, NULL, 1,
 | |
|  "undo_key_insert_with_root", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_KEY_DELETE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_key_delete, NULL, 1,
 | |
|  "undo_key_delete", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_KEY_DELETE_WITH_ROOT=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE + KEY_NR_STORE_SIZE + PAGE_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_key_delete, NULL, 1,
 | |
|  "undo_key_delete_with_root", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_PREPARE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "prepare", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_PREPARE_WITH_UNDO_PURGE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, LSN_STORE_SIZE, NULL, NULL, NULL, 1,
 | |
|  "prepare_with_undo_purge", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_COMMIT=
 | |
| {LOGRECTYPE_FIXEDLENGTH, 0, 0, NULL,
 | |
|  write_hook_for_commit, NULL, 0, "commit", LOGREC_IS_GROUP_ITSELF, NULL,
 | |
|  NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_COMMIT_WITH_UNDO_PURGE=
 | |
| {LOGRECTYPE_PSEUDOFIXEDLENGTH, 5, 5, NULL, write_hook_for_commit, NULL, 1,
 | |
|  "commit_with_undo_purge", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_CHECKPOINT=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "checkpoint", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_CREATE_TABLE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 1 + 2, NULL, NULL, NULL, 0,
 | |
| "redo_create_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_RENAME_TABLE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "redo_rename_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_DROP_TABLE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "redo_drop_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_DELETE_ALL=
 | |
| {LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE, FILEID_STORE_SIZE,
 | |
|  NULL, write_hook_for_redo_delete_all, NULL, 0,
 | |
|  "redo_delete_all", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_REPAIR_TABLE=
 | |
| {LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE + 8 + 8, FILEID_STORE_SIZE + 8 + 8,
 | |
|  NULL, NULL, NULL, 0,
 | |
|  "redo_repair_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_FILE_ID=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 2, NULL, write_hook_for_file_id, NULL, 0,
 | |
|  "file_id", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_LONG_TRANSACTION_ID=
 | |
| {LOGRECTYPE_FIXEDLENGTH, 6, 6, NULL, NULL, NULL, 0,
 | |
|  "long_transaction_id", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_INCOMPLETE_LOG=
 | |
| {LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE, FILEID_STORE_SIZE,
 | |
|  NULL, NULL, NULL, 0,
 | |
|  "incomplete_log", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_INCOMPLETE_GROUP=
 | |
| {LOGRECTYPE_FIXEDLENGTH, 0, 0,
 | |
|  NULL, NULL, NULL, 0,
 | |
|  "incomplete_group", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_UNDO_BULK_INSERT=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0,
 | |
|  LSN_STORE_SIZE + FILEID_STORE_SIZE,
 | |
|  NULL, write_hook_for_undo_bulk_insert, NULL, 1,
 | |
|  "undo_bulk_insert", LOGREC_LAST_IN_GROUP, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_REDO_BITMAP_NEW_PAGE=
 | |
| {LOGRECTYPE_FIXEDLENGTH, FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2,
 | |
|  FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2,
 | |
|  NULL, NULL, NULL, 0,
 | |
|  "redo_create_bitmap", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_IMPORTED_TABLE=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "imported_table", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| static LOG_DESC INIT_LOGREC_DEBUG_INFO=
 | |
| {LOGRECTYPE_VARIABLE_LENGTH, 0, 0, NULL, NULL, NULL, 0,
 | |
|  "info", LOGREC_IS_GROUP_ITSELF, NULL, NULL};
 | |
| 
 | |
| const myf log_write_flags= MY_WME | MY_NABP | MY_WAIT_IF_FULL;
 | |
| 
 | |
| void translog_table_init()
 | |
| {
 | |
|   int i;
 | |
|   log_record_type_descriptor[LOGREC_RESERVED_FOR_CHUNKS23]=
 | |
|     INIT_LOGREC_RESERVED_FOR_CHUNKS23;
 | |
|   log_record_type_descriptor[LOGREC_REDO_INSERT_ROW_HEAD]=
 | |
|     INIT_LOGREC_REDO_INSERT_ROW_HEAD;
 | |
|   log_record_type_descriptor[LOGREC_REDO_INSERT_ROW_TAIL]=
 | |
|     INIT_LOGREC_REDO_INSERT_ROW_TAIL;
 | |
|   log_record_type_descriptor[LOGREC_REDO_NEW_ROW_HEAD]=
 | |
|     INIT_LOGREC_REDO_NEW_ROW_HEAD;
 | |
|   log_record_type_descriptor[LOGREC_REDO_NEW_ROW_TAIL]=
 | |
|     INIT_LOGREC_REDO_NEW_ROW_TAIL;
 | |
|   log_record_type_descriptor[LOGREC_REDO_INSERT_ROW_BLOBS]=
 | |
|     INIT_LOGREC_REDO_INSERT_ROW_BLOBS;
 | |
|   log_record_type_descriptor[LOGREC_REDO_PURGE_ROW_HEAD]=
 | |
|     INIT_LOGREC_REDO_PURGE_ROW_HEAD;
 | |
|   log_record_type_descriptor[LOGREC_REDO_PURGE_ROW_TAIL]=
 | |
|     INIT_LOGREC_REDO_PURGE_ROW_TAIL;
 | |
|   log_record_type_descriptor[LOGREC_REDO_FREE_BLOCKS]=
 | |
|     INIT_LOGREC_REDO_FREE_BLOCKS;
 | |
|   log_record_type_descriptor[LOGREC_REDO_FREE_HEAD_OR_TAIL]=
 | |
|     INIT_LOGREC_REDO_FREE_HEAD_OR_TAIL;
 | |
|   log_record_type_descriptor[LOGREC_REDO_DELETE_ROW]=
 | |
|     INIT_LOGREC_REDO_DELETE_ROW;
 | |
|   log_record_type_descriptor[LOGREC_REDO_UPDATE_ROW_HEAD]=
 | |
|     INIT_LOGREC_REDO_UPDATE_ROW_HEAD;
 | |
|   log_record_type_descriptor[LOGREC_REDO_INDEX]=
 | |
|     INIT_LOGREC_REDO_INDEX;
 | |
|   log_record_type_descriptor[LOGREC_REDO_INDEX_NEW_PAGE]=
 | |
|     INIT_LOGREC_REDO_INDEX_NEW_PAGE;
 | |
|   log_record_type_descriptor[LOGREC_REDO_INDEX_FREE_PAGE]=
 | |
|     INIT_LOGREC_REDO_INDEX_FREE_PAGE;
 | |
|   log_record_type_descriptor[LOGREC_REDO_UNDELETE_ROW]=
 | |
|     INIT_LOGREC_REDO_UNDELETE_ROW;
 | |
|   log_record_type_descriptor[LOGREC_CLR_END]=
 | |
|     INIT_LOGREC_CLR_END;
 | |
|   log_record_type_descriptor[LOGREC_PURGE_END]=
 | |
|     INIT_LOGREC_PURGE_END;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_ROW_INSERT]=
 | |
|     INIT_LOGREC_UNDO_ROW_INSERT;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_ROW_DELETE]=
 | |
|     INIT_LOGREC_UNDO_ROW_DELETE;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_ROW_UPDATE]=
 | |
|     INIT_LOGREC_UNDO_ROW_UPDATE;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_KEY_INSERT]=
 | |
|     INIT_LOGREC_UNDO_KEY_INSERT;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_KEY_INSERT_WITH_ROOT]=
 | |
|     INIT_LOGREC_UNDO_KEY_INSERT_WITH_ROOT;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_KEY_DELETE]=
 | |
|     INIT_LOGREC_UNDO_KEY_DELETE;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_KEY_DELETE_WITH_ROOT]=
 | |
|     INIT_LOGREC_UNDO_KEY_DELETE_WITH_ROOT;
 | |
|   log_record_type_descriptor[LOGREC_PREPARE]=
 | |
|     INIT_LOGREC_PREPARE;
 | |
|   log_record_type_descriptor[LOGREC_PREPARE_WITH_UNDO_PURGE]=
 | |
|     INIT_LOGREC_PREPARE_WITH_UNDO_PURGE;
 | |
|   log_record_type_descriptor[LOGREC_COMMIT]=
 | |
|     INIT_LOGREC_COMMIT;
 | |
|   log_record_type_descriptor[LOGREC_COMMIT_WITH_UNDO_PURGE]=
 | |
|     INIT_LOGREC_COMMIT_WITH_UNDO_PURGE;
 | |
|   log_record_type_descriptor[LOGREC_CHECKPOINT]=
 | |
|     INIT_LOGREC_CHECKPOINT;
 | |
|   log_record_type_descriptor[LOGREC_REDO_CREATE_TABLE]=
 | |
|     INIT_LOGREC_REDO_CREATE_TABLE;
 | |
|   log_record_type_descriptor[LOGREC_REDO_RENAME_TABLE]=
 | |
|     INIT_LOGREC_REDO_RENAME_TABLE;
 | |
|   log_record_type_descriptor[LOGREC_REDO_DROP_TABLE]=
 | |
|     INIT_LOGREC_REDO_DROP_TABLE;
 | |
|   log_record_type_descriptor[LOGREC_REDO_DELETE_ALL]=
 | |
|     INIT_LOGREC_REDO_DELETE_ALL;
 | |
|   log_record_type_descriptor[LOGREC_REDO_REPAIR_TABLE]=
 | |
|     INIT_LOGREC_REDO_REPAIR_TABLE;
 | |
|   log_record_type_descriptor[LOGREC_FILE_ID]=
 | |
|     INIT_LOGREC_FILE_ID;
 | |
|   log_record_type_descriptor[LOGREC_LONG_TRANSACTION_ID]=
 | |
|     INIT_LOGREC_LONG_TRANSACTION_ID;
 | |
|   log_record_type_descriptor[LOGREC_INCOMPLETE_LOG]=
 | |
|     INIT_LOGREC_INCOMPLETE_LOG;
 | |
|   log_record_type_descriptor[LOGREC_INCOMPLETE_GROUP]=
 | |
|     INIT_LOGREC_INCOMPLETE_GROUP;
 | |
|   log_record_type_descriptor[LOGREC_UNDO_BULK_INSERT]=
 | |
|     INIT_LOGREC_UNDO_BULK_INSERT;
 | |
|   log_record_type_descriptor[LOGREC_REDO_BITMAP_NEW_PAGE]=
 | |
|     INIT_LOGREC_REDO_BITMAP_NEW_PAGE;
 | |
|   log_record_type_descriptor[LOGREC_IMPORTED_TABLE]=
 | |
|     INIT_LOGREC_IMPORTED_TABLE;
 | |
|   log_record_type_descriptor[LOGREC_DEBUG_INFO]=
 | |
|     INIT_LOGREC_DEBUG_INFO;
 | |
| 
 | |
|   for (i= LOGREC_FIRST_FREE; i < LOGREC_NUMBER_OF_TYPES; i++)
 | |
|     log_record_type_descriptor[i].rclass= LOGRECTYPE_NOT_ALLOWED;
 | |
| #ifndef DBUG_OFF
 | |
|   check_translog_description_table(LOGREC_FIRST_FREE -1);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| /* all possible flags page overheads */
 | |
| static uint page_overhead[TRANSLOG_FLAGS_NUM];
 | |
| 
 | |
| typedef struct st_translog_validator_data
 | |
| {
 | |
|   TRANSLOG_ADDRESS *addr;
 | |
|   my_bool was_recovered;
 | |
| } TRANSLOG_VALIDATOR_DATA;
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check cursor/buffer consistence
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_check_cursor
 | |
|     cursor               cursor which will be checked
 | |
| */
 | |
| 
 | |
| static void translog_check_cursor(struct st_buffer_cursor *cursor
 | |
|                                  __attribute__((unused)))
 | |
| {
 | |
|   DBUG_ASSERT(cursor->chaser ||
 | |
|               ((ulong) (cursor->ptr - cursor->buffer->buffer) ==
 | |
|                cursor->buffer->size));
 | |
|   DBUG_ASSERT(cursor->buffer->buffer_no == cursor->buffer_no);
 | |
|   DBUG_ASSERT((cursor->ptr -cursor->buffer->buffer) %TRANSLOG_PAGE_SIZE ==
 | |
|               cursor->current_page_fill % TRANSLOG_PAGE_SIZE);
 | |
|   DBUG_ASSERT(cursor->current_page_fill <= TRANSLOG_PAGE_SIZE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief switch the loghandler in read only mode in case of write error
 | |
| */
 | |
| 
 | |
| void translog_stop_writing()
 | |
| {
 | |
|   DBUG_ENTER("translog_stop_writing");
 | |
|   DBUG_PRINT("error", ("errno: %d   my_errno: %d", errno, my_errno));
 | |
|   translog_status= (translog_status == TRANSLOG_SHUTDOWN ?
 | |
|                     TRANSLOG_UNINITED :
 | |
|                     TRANSLOG_READONLY);
 | |
|   log_descriptor.is_everything_flushed= 1;
 | |
|   log_descriptor.open_flags= O_BINARY | O_RDONLY;
 | |
|   DBUG_ASSERT(0);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Get file name of the log by log number
 | |
| 
 | |
|   @param file_no         Number of the log we want to open
 | |
|   @param path            Pointer to buffer where file name will be
 | |
|                          stored (must be FN_REFLEN bytes at least)
 | |
| 
 | |
|   @return pointer to path
 | |
| */
 | |
| 
 | |
| char *translog_filename_by_fileno(uint32 file_no, char *path)
 | |
| {
 | |
|   char buff[11], *end;
 | |
|   uint length;
 | |
|   DBUG_ENTER("translog_filename_by_fileno");
 | |
|   DBUG_ASSERT(file_no <= 0xfffffff);
 | |
| 
 | |
|   /* log_descriptor.directory is already formatted */
 | |
|   end= strxmov(path, log_descriptor.directory, "aria_log.0000000", NullS);
 | |
|   length= (uint) (int10_to_str(file_no, buff, 10) - buff);
 | |
|   strmov(end - length +1, buff);
 | |
| 
 | |
|   DBUG_PRINT("info", ("Path: '%s'  path: %p", path, path));
 | |
|   DBUG_RETURN(path);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Create log file with given number without cache
 | |
| 
 | |
|   @param file_no         Number of the log we want to open
 | |
| 
 | |
|   retval -1  error
 | |
|   retval # file descriptor number
 | |
| */
 | |
| 
 | |
| static File create_logfile_by_number_no_cache(uint32 file_no)
 | |
| {
 | |
|   File file;
 | |
|   char path[FN_REFLEN];
 | |
|   DBUG_ENTER("create_logfile_by_number_no_cache");
 | |
| 
 | |
|   if (translog_status != TRANSLOG_OK)
 | |
|      DBUG_RETURN(-1);
 | |
| 
 | |
|   /* TODO: add O_DIRECT to open flags (when buffer is aligned) */
 | |
|   if ((file= mysql_file_create(key_file_translog,
 | |
|                                translog_filename_by_fileno(file_no, path),
 | |
|                                0, O_BINARY | O_RDWR | O_CLOEXEC, MYF(MY_WME))) < 0)
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Error %d during creating file '%s'", errno, path));
 | |
|     translog_stop_writing();
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
|   if (sync_log_dir >= TRANSLOG_SYNC_DIR_NEWFILE &&
 | |
|       sync_dir(log_descriptor.directory_fd, MYF(MY_WME | MY_IGNORE_BADFD)))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Error %d during syncing directory '%s'",
 | |
|                          errno, log_descriptor.directory));
 | |
|     mysql_file_close(file, MYF(0));
 | |
|     translog_stop_writing();
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
|   DBUG_PRINT("info", ("File: '%s'  handler: %d", path, file));
 | |
|   DBUG_RETURN(file);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief Open (not create) log file with given number without cache
 | |
| 
 | |
|   @param file_no         Number of the log we want to open
 | |
| 
 | |
|   retval -1  error
 | |
|   retval # file descriptor number
 | |
| */
 | |
| 
 | |
| static File open_logfile_by_number_no_cache(uint32 file_no)
 | |
| {
 | |
|   File file;
 | |
|   char path[FN_REFLEN];
 | |
|   DBUG_ENTER("open_logfile_by_number_no_cache");
 | |
| 
 | |
|   /* TODO: add O_DIRECT to open flags (when buffer is aligned) */
 | |
|   /* TODO: use mysql_file_create() */
 | |
|   if ((file= mysql_file_open(key_file_translog,
 | |
|                              translog_filename_by_fileno(file_no, path),
 | |
|                              log_descriptor.open_flags | O_CLOEXEC,
 | |
|                              MYF(MY_WME))) < 0)
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Error %d during opening file '%s'", errno, path));
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
|   DBUG_PRINT("info", ("File: '%s'  handler: %d", path, file));
 | |
|   DBUG_RETURN(file);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief get file descriptor by given number using cache
 | |
| 
 | |
|   @param file_no         Number of the log we want to open
 | |
| 
 | |
|   retval # file descriptor
 | |
|   retval NULL file is not opened
 | |
| */
 | |
| 
 | |
| static TRANSLOG_FILE *get_logfile_by_number(uint32 file_no)
 | |
| {
 | |
|   TRANSLOG_FILE *file;
 | |
|   DBUG_ENTER("get_logfile_by_number");
 | |
|   mysql_rwlock_rdlock(&log_descriptor.open_files_lock);
 | |
|   if (log_descriptor.max_file - file_no >=
 | |
|       log_descriptor.open_files.elements)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("File #%u is not opened", file_no));
 | |
|     mysql_rwlock_unlock(&log_descriptor.open_files_lock);
 | |
|     DBUG_RETURN(NULL);
 | |
|   }
 | |
|   DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|               log_descriptor.open_files.elements);
 | |
|   DBUG_ASSERT(log_descriptor.max_file >= file_no);
 | |
|   DBUG_ASSERT(log_descriptor.min_file <= file_no);
 | |
| 
 | |
|   file= *dynamic_element(&log_descriptor.open_files,
 | |
|                          log_descriptor.max_file - file_no, TRANSLOG_FILE **);
 | |
|   mysql_rwlock_unlock(&log_descriptor.open_files_lock);
 | |
|   DBUG_PRINT("info", ("File %p File no: %u, File handler: %d",
 | |
|                        file, file_no,
 | |
|                       (file ? file->handler.file : -1)));
 | |
|   DBUG_ASSERT(!file || file->number == file_no);
 | |
|   DBUG_RETURN(file);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief get current file descriptor
 | |
| 
 | |
|   retval # file descriptor
 | |
| */
 | |
| 
 | |
| static TRANSLOG_FILE *get_current_logfile()
 | |
| {
 | |
|   TRANSLOG_FILE *file;
 | |
|   DBUG_ENTER("get_current_logfile");
 | |
|   mysql_rwlock_rdlock(&log_descriptor.open_files_lock);
 | |
|   DBUG_PRINT("info", ("max_file: %lu  min_file: %lu  open_files: %lu",
 | |
|                       (ulong) log_descriptor.max_file,
 | |
|                       (ulong) log_descriptor.min_file,
 | |
|                       (ulong) log_descriptor.open_files.elements));
 | |
|   DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|               log_descriptor.open_files.elements);
 | |
|   file= *dynamic_element(&log_descriptor.open_files, 0, TRANSLOG_FILE **);
 | |
|   mysql_rwlock_unlock(&log_descriptor.open_files_lock);
 | |
|   DBUG_RETURN(file);
 | |
| }
 | |
| 
 | |
| uchar	maria_trans_file_magic[]=
 | |
| { (uchar) 254, (uchar) 254, (uchar) 11, '\001', 'M', 'A', 'R', 'I', 'A',
 | |
|  'L', 'O', 'G' };
 | |
| #define LOG_HEADER_DATA_SIZE (sizeof(maria_trans_file_magic) + \
 | |
|                               8 + 4 + 4 + 4 + 2 + 3 + \
 | |
|                               LSN_STORE_SIZE)
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Write log file page header in the just opened new log file
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_file_header();
 | |
| 
 | |
|    NOTES
 | |
|     First page is just a marker page; We don't store any real log data in it.
 | |
| 
 | |
|   RETURN
 | |
|     0 OK
 | |
|     1 ERROR
 | |
| */
 | |
| 
 | |
| static my_bool translog_write_file_header()
 | |
| {
 | |
|   TRANSLOG_FILE *file;
 | |
|   ulonglong timestamp;
 | |
|   uchar page_buff[TRANSLOG_PAGE_SIZE], *page= page_buff;
 | |
|   my_bool rc;
 | |
|   DBUG_ENTER("translog_write_file_header");
 | |
| 
 | |
|   /* file tag */
 | |
|   memcpy(page, maria_trans_file_magic, sizeof(maria_trans_file_magic));
 | |
|   page+= sizeof(maria_trans_file_magic);
 | |
|   /* timestamp */
 | |
|   timestamp= my_hrtime().val;
 | |
|   int8store(page, timestamp);
 | |
|   page+= 8;
 | |
|   /* maria version */
 | |
|   int4store(page, TRANSLOG_VERSION_ID);
 | |
|   page+= 4;
 | |
|   /* mysql version (MYSQL_VERSION_ID) */
 | |
|   int4store(page, log_descriptor.server_version);
 | |
|   page+= 4;
 | |
|   /* server ID */
 | |
|   int4store(page, log_descriptor.server_id);
 | |
|   page+= 4;
 | |
|   /* loghandler page_size */
 | |
|   int2store(page, TRANSLOG_PAGE_SIZE - 1);
 | |
|   page+= 2;
 | |
|   /* file number */
 | |
|   int3store(page, LSN_FILE_NO(log_descriptor.horizon));
 | |
|   page+= 3;
 | |
|   lsn_store(page, LSN_IMPOSSIBLE);
 | |
|   page+= LSN_STORE_SIZE;
 | |
|   memset(page, TRANSLOG_FILLER, sizeof(page_buff) - (page- page_buff));
 | |
| 
 | |
|   file= get_current_logfile();
 | |
|   rc= my_pwrite(file->handler.file, page_buff, sizeof(page_buff), 0,
 | |
|                 log_write_flags) != 0;
 | |
|   /*
 | |
|     Dropping the flag in such way can make false alarm: signalling than the
 | |
|     file in not sync when it is sync, but the situation is quite rare and
 | |
|     protections with mutexes give much more overhead to the whole engine
 | |
|   */
 | |
|   file->is_sync= 0;
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   @brief write the new LSN on the given file header
 | |
| 
 | |
|   @param file            The file descriptor
 | |
|   @param lsn             That LSN which should be written
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_max_lsn_to_header(File file, LSN lsn)
 | |
| {
 | |
|   uchar lsn_buff[LSN_STORE_SIZE];
 | |
|   my_bool rc;
 | |
|   DBUG_ENTER("translog_max_lsn_to_header");
 | |
|   DBUG_PRINT("enter", ("File descriptor: %ld  "
 | |
|                        "lsn: " LSN_FMT,
 | |
|                        (long) file,
 | |
|                        LSN_IN_PARTS(lsn)));
 | |
| 
 | |
|   lsn_store(lsn_buff, lsn);
 | |
| 
 | |
|   rc= (my_pwrite(file, lsn_buff,
 | |
|                  LSN_STORE_SIZE,
 | |
|                  (LOG_HEADER_DATA_SIZE - LSN_STORE_SIZE),
 | |
|                  log_write_flags) != 0 ||
 | |
|        mysql_file_sync(file, MYF(MY_WME)) != 0);
 | |
|   /*
 | |
|     We should not increase counter in case of error above, but it is so
 | |
|     unlikely that we can ignore this case
 | |
|   */
 | |
|   translog_syncs++;
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Extract hander file information from loghandler file page
 | |
| 
 | |
|   @param desc header information descriptor to be filled with information
 | |
|   @param page_buff buffer with the page content
 | |
| */
 | |
| 
 | |
| void translog_interpret_file_header(LOGHANDLER_FILE_INFO *desc,
 | |
|                                     uchar *page_buff)
 | |
| {
 | |
|   uchar *ptr;
 | |
| 
 | |
|   ptr= page_buff + sizeof(maria_trans_file_magic);
 | |
|   desc->timestamp= uint8korr(ptr);
 | |
|   ptr+= 8;
 | |
|   desc->maria_version= uint4korr(ptr);
 | |
|   ptr+= 4;
 | |
|   desc->mysql_version= uint4korr(ptr);
 | |
|   ptr+= 4;
 | |
|   desc->server_id= uint4korr(ptr);
 | |
|   ptr+= 4;
 | |
|   desc->page_size= uint2korr(ptr) + 1;
 | |
|   ptr+= 2;
 | |
|   desc->file_number= uint3korr(ptr);
 | |
|   ptr+= 3;
 | |
|   desc->max_lsn= lsn_korr(ptr);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Read hander file information from loghandler file
 | |
| 
 | |
|   @param desc header information descriptor to be filled with information
 | |
|   @param file file descriptor to read
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| my_bool translog_read_file_header(LOGHANDLER_FILE_INFO *desc, File file)
 | |
| {
 | |
|   uchar page_buff[LOG_HEADER_DATA_SIZE];
 | |
|   DBUG_ENTER("translog_read_file_header");
 | |
| 
 | |
|   if (mysql_file_pread(file, page_buff,
 | |
|                sizeof(page_buff), 0, MYF(MY_FNABP | MY_WME)))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("log read fail error: %d", my_errno));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   translog_interpret_file_header(desc, page_buff);
 | |
|   DBUG_PRINT("info", ("timestamp: %llu  aria ver: %lu mysql ver: %lu  "
 | |
|                       "server id %lu page size %lu file number %lu  "
 | |
|                       "max lsn: " LSN_FMT,
 | |
|                       (ulonglong) desc->timestamp,
 | |
|                       (ulong) desc->maria_version,
 | |
|                       (ulong) desc->mysql_version,
 | |
|                       (ulong) desc->server_id,
 | |
|                       desc->page_size, (ulong) desc->file_number,
 | |
|                       LSN_IN_PARTS(desc->max_lsn)));
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief set the lsn to the files from_file - to_file if it is greater
 | |
|   then written in the file
 | |
| 
 | |
|   @param from_file       first file number (min)
 | |
|   @param to_file         last file number (max)
 | |
|   @param lsn             the lsn for writing
 | |
|   @param is_locked       true if current thread locked the log handler
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_set_lsn_for_files(uint32 from_file, uint32 to_file,
 | |
|                                           LSN lsn, my_bool is_locked)
 | |
| {
 | |
|   uint32 file;
 | |
|   DBUG_ENTER("translog_set_lsn_for_files");
 | |
|   DBUG_PRINT("enter", ("From: %lu  to: %lu  lsn: " LSN_FMT "  locked: %d",
 | |
|                        (ulong) from_file, (ulong) to_file,
 | |
|                        LSN_IN_PARTS(lsn),
 | |
|                        is_locked));
 | |
|   DBUG_ASSERT(from_file <= to_file);
 | |
|   DBUG_ASSERT(from_file > 0); /* we have not file 0 */
 | |
| 
 | |
|   /* Checks the current file (not finished yet file) */
 | |
|   if (!is_locked)
 | |
|     translog_lock();
 | |
|   if (to_file == (uint32) LSN_FILE_NO(log_descriptor.horizon))
 | |
|   {
 | |
|     if (likely(cmp_translog_addr(lsn, log_descriptor.max_lsn) > 0))
 | |
|       log_descriptor.max_lsn= lsn;
 | |
|     to_file--;
 | |
|   }
 | |
|   if (!is_locked)
 | |
|     translog_unlock();
 | |
| 
 | |
|   /* Checks finished files if they are */
 | |
|   mysql_mutex_lock(&log_descriptor.file_header_lock);
 | |
|   for (file= from_file; file <= to_file; file++)
 | |
|   {
 | |
|     LOGHANDLER_FILE_INFO info;
 | |
|     File fd;
 | |
| 
 | |
|     fd= open_logfile_by_number_no_cache(file);
 | |
|     if ((fd < 0) ||
 | |
|         ((translog_read_file_header(&info, fd) ||
 | |
|           (cmp_translog_addr(lsn, info.max_lsn) > 0 &&
 | |
|            translog_max_lsn_to_header(fd, lsn))) |
 | |
|           mysql_file_close(fd, MYF(MY_WME))))
 | |
|     {
 | |
|       translog_stop_writing();
 | |
|       mysql_mutex_unlock(&log_descriptor.file_header_lock);
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
|   mysql_mutex_unlock(&log_descriptor.file_header_lock);
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* descriptor of file in unfinished_files */
 | |
| struct st_file_counter
 | |
| {
 | |
|   uint32 file;            /* file number */
 | |
|   uint32 counter;         /* counter for started writes */
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief mark file "in progress" (for multi-group records)
 | |
| 
 | |
|   @param file            log file number
 | |
| */
 | |
| 
 | |
| static void translog_mark_file_unfinished(uint32 file)
 | |
| {
 | |
|   int place, i;
 | |
|   struct st_file_counter fc, *fc_ptr;
 | |
| 
 | |
|   DBUG_ENTER("translog_mark_file_unfinished");
 | |
|   DBUG_PRINT("enter", ("file: %lu", (ulong) file));
 | |
| 
 | |
|   fc.file= file; fc.counter= 1;
 | |
|   mysql_mutex_lock(&log_descriptor.unfinished_files_lock);
 | |
| 
 | |
|   if (log_descriptor.unfinished_files.elements == 0)
 | |
|   {
 | |
|     insert_dynamic(&log_descriptor.unfinished_files, (uchar*) &fc);
 | |
|     DBUG_PRINT("info", ("The first element inserted"));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   for (place= log_descriptor.unfinished_files.elements - 1;
 | |
|        place >= 0;
 | |
|        place--)
 | |
|   {
 | |
|     fc_ptr= dynamic_element(&log_descriptor.unfinished_files,
 | |
|                             place, struct st_file_counter *);
 | |
|     if (fc_ptr->file <= file)
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (place >= 0 && fc_ptr->file == file)
 | |
|   {
 | |
|      fc_ptr->counter++;
 | |
|      DBUG_PRINT("info", ("counter increased"));
 | |
|      goto end;
 | |
|   }
 | |
| 
 | |
|   if (place == (int)log_descriptor.unfinished_files.elements)
 | |
|   {
 | |
|     insert_dynamic(&log_descriptor.unfinished_files, (uchar*) &fc);
 | |
|     DBUG_PRINT("info", ("The last element inserted"));
 | |
|     goto end;
 | |
|   }
 | |
|   /* shift and assign new element */
 | |
|   insert_dynamic(&log_descriptor.unfinished_files,
 | |
|                  (uchar*)
 | |
|                  dynamic_element(&log_descriptor.unfinished_files,
 | |
|                                  log_descriptor.unfinished_files.elements- 1,
 | |
|                                  struct st_file_counter *));
 | |
|   for(i= log_descriptor.unfinished_files.elements - 1; i > place; i--)
 | |
|   {
 | |
|     /* we do not use set_dynamic() to avoid unneeded checks */
 | |
|     memcpy(dynamic_element(&log_descriptor.unfinished_files,
 | |
|                            i, struct st_file_counter *),
 | |
|            dynamic_element(&log_descriptor.unfinished_files,
 | |
|                            i + 1, struct st_file_counter *),
 | |
|            sizeof(struct st_file_counter));
 | |
|   }
 | |
|   memcpy(dynamic_element(&log_descriptor.unfinished_files,
 | |
|                          place + 1, struct st_file_counter *),
 | |
|          &fc, sizeof(struct st_file_counter));
 | |
| end:
 | |
|   mysql_mutex_unlock(&log_descriptor.unfinished_files_lock);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief remove file mark "in progress" (for multi-group records)
 | |
| 
 | |
|   @param file            log file number
 | |
| */
 | |
| 
 | |
| static void translog_mark_file_finished(uint32 file)
 | |
| {
 | |
|   int i;
 | |
|   struct st_file_counter *UNINIT_VAR(fc_ptr);
 | |
|   DBUG_ENTER("translog_mark_file_finished");
 | |
|   DBUG_PRINT("enter", ("file: %lu", (ulong) file));
 | |
| 
 | |
|   mysql_mutex_lock(&log_descriptor.unfinished_files_lock);
 | |
| 
 | |
|   DBUG_ASSERT(log_descriptor.unfinished_files.elements > 0);
 | |
|   for (i= 0;
 | |
|        i < (int) log_descriptor.unfinished_files.elements;
 | |
|        i++)
 | |
|   {
 | |
|     fc_ptr= dynamic_element(&log_descriptor.unfinished_files,
 | |
|                             i, struct st_file_counter *);
 | |
|     if (fc_ptr->file == file)
 | |
|     {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   DBUG_ASSERT(i < (int) log_descriptor.unfinished_files.elements);
 | |
| 
 | |
|   if (! --fc_ptr->counter)
 | |
|     delete_dynamic_element(&log_descriptor.unfinished_files, i);
 | |
|   mysql_mutex_unlock(&log_descriptor.unfinished_files_lock);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief get max LSN of the record which parts stored in this file
 | |
| 
 | |
|   @param file            file number
 | |
| 
 | |
|   @return requested LSN or LSN_IMPOSSIBLE/LSN_ERROR
 | |
|     @retval LSN_IMPOSSIBLE File is still not finished
 | |
|     @retval LSN_ERROR Error opening file
 | |
|     @retval # LSN of the record which parts stored in this file
 | |
| */
 | |
| 
 | |
| LSN translog_get_file_max_lsn_stored(uint32 file)
 | |
| {
 | |
|   uint32 limit= FILENO_IMPOSSIBLE;
 | |
|   DBUG_ENTER("translog_get_file_max_lsn_stored");
 | |
|   DBUG_PRINT("enter", ("file: %lu", (ulong)file));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   mysql_mutex_lock(&log_descriptor.unfinished_files_lock);
 | |
| 
 | |
|   /* find file with minimum file number "in progress" */
 | |
|   if (log_descriptor.unfinished_files.elements > 0)
 | |
|   {
 | |
|     struct st_file_counter *fc_ptr;
 | |
|     fc_ptr= dynamic_element(&log_descriptor.unfinished_files,
 | |
|                             0, struct st_file_counter *);
 | |
|     limit= fc_ptr->file; /* minimal file number "in progress" */
 | |
|   }
 | |
|   mysql_mutex_unlock(&log_descriptor.unfinished_files_lock);
 | |
| 
 | |
|   /*
 | |
|     if there is no "in progress file" then unfinished file is in progress
 | |
|     for sure
 | |
|   */
 | |
|   if (limit == FILENO_IMPOSSIBLE)
 | |
|   {
 | |
|     TRANSLOG_ADDRESS horizon= translog_get_horizon();
 | |
|     limit= LSN_FILE_NO(horizon);
 | |
|   }
 | |
| 
 | |
|   if (file >= limit)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("The file in in progress"));
 | |
|     DBUG_RETURN(LSN_IMPOSSIBLE);
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     LOGHANDLER_FILE_INFO info;
 | |
|     File fd;
 | |
| 
 | |
|     fd= open_logfile_by_number_no_cache(file);
 | |
|     if(fd < 0) 
 | |
|     {
 | |
|       DBUG_PRINT("error", ("Can't open file"));
 | |
|       DBUG_RETURN(LSN_ERROR);
 | |
|     }
 | |
| 
 | |
|     if (translog_read_file_header(&info, fd))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("Can't read file header"));
 | |
|       info.max_lsn= LSN_ERROR;
 | |
|     }
 | |
| 
 | |
|     if (mysql_file_close(fd, MYF(MY_WME)))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("Can't close file"));
 | |
|       info.max_lsn= LSN_ERROR;
 | |
|     }
 | |
| 
 | |
|     DBUG_PRINT("info", ("Max lsn: " LSN_FMT, LSN_IN_PARTS(info.max_lsn)));
 | |
|     DBUG_RETURN(info.max_lsn);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Initialize transaction log file buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_buffer_init()
 | |
|     buffer               The buffer to initialize
 | |
|     num                  Number of this buffer
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_buffer_init(struct st_translog_buffer *buffer, int num)
 | |
| {
 | |
|   DBUG_ENTER("translog_buffer_init");
 | |
|   buffer->pre_force_close_horizon=
 | |
|     buffer->prev_last_lsn= buffer->last_lsn=
 | |
|     LSN_IMPOSSIBLE;
 | |
|   DBUG_PRINT("info", ("last_lsn  and prev_last_lsn set to 0  buffer: %p",
 | |
|                       buffer));
 | |
| 
 | |
|   buffer->buffer_no= (uint8) num;
 | |
|   /* This Buffer File */
 | |
|   buffer->file= NULL;
 | |
|   buffer->overlay= 0;
 | |
|   /* cache for current log */
 | |
|   memset(buffer->buffer, TRANSLOG_FILLER, TRANSLOG_WRITE_BUFFER);
 | |
|   /* Buffer size */
 | |
|   buffer->size= 0;
 | |
|   buffer->skipped_data= 0;
 | |
|   /* cond of thread which is waiting for buffer filling */
 | |
|   if (mysql_cond_init(key_TRANSLOG_BUFFER_waiting_filling_buffer,
 | |
|                       &buffer->waiting_filling_buffer, 0))
 | |
|     DBUG_RETURN(1);
 | |
|   /* Number of records which are in copy progress */
 | |
|   buffer->copy_to_buffer_in_progress= 0;
 | |
|   /* list of waiting buffer ready threads */
 | |
|   buffer->waiting_flush= 0;
 | |
|   /*
 | |
|     Buffers locked by the following mutex. As far as buffers create logical
 | |
|     circle (after last buffer goes first) it trigger false alarm of deadlock
 | |
|     detect system, so we remove check of deadlock for this buffers. Indeed
 | |
|     all mutex locks concentrated around current buffer except flushing
 | |
|     thread (but it is only one thread). One thread can't take more then
 | |
|     2 buffer locks at once. So deadlock is impossible here.
 | |
| 
 | |
|     To prevent false alarm of dead lock detection we switch dead lock
 | |
|     detection for one buffer in the middle of the buffers chain. Excluding
 | |
|     only one of eight buffers from deadlock detection hardly can hide other
 | |
|     possible problems which include this mutexes.
 | |
|   */
 | |
| 
 | |
|   if (mysql_mutex_init(key_TRANSLOG_BUFFER_mutex,
 | |
|                        &buffer->mutex, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_cond_init(key_TRANSLOG_BUFFER_prev_sent_to_disk_cond,
 | |
|                       &buffer->prev_sent_to_disk_cond, 0))
 | |
|     DBUG_RETURN(1);
 | |
|   mysql_mutex_setflags(&buffer->mutex, MYF_NO_DEADLOCK_DETECTION);
 | |
|   buffer->is_closing_buffer= 0;
 | |
|   buffer->prev_sent_to_disk= LSN_IMPOSSIBLE;
 | |
|   buffer->prev_buffer_offset= LSN_IMPOSSIBLE;
 | |
|   buffer->ver= 0;
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief close transaction log file by descriptor
 | |
| 
 | |
|   @param file            pagegecache file descriptor reference
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval 0  OK
 | |
|     @retval 1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_close_log_file(TRANSLOG_FILE *file)
 | |
| {
 | |
|   int rc= 0;
 | |
|   flush_pagecache_blocks(log_descriptor.pagecache, &file->handler,
 | |
|                          FLUSH_RELEASE);
 | |
|   /*
 | |
|     Sync file when we close it
 | |
|     TODO: sync only we have changed the log
 | |
|   */
 | |
|   if (!file->is_sync)
 | |
|   {
 | |
|     rc= mysql_file_sync(file->handler.file, MYF(MY_WME));
 | |
|     translog_syncs++;
 | |
|   }
 | |
|   rc|= mysql_file_close(file->handler.file, MYF(MY_WME));
 | |
|   my_free(file);
 | |
|   return MY_TEST(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Initializes TRANSLOG_FILE structure
 | |
| 
 | |
|   @param file            reference on the file to initialize
 | |
|   @param number          file number
 | |
|   @param is_sync         is file synced on disk
 | |
| */
 | |
| 
 | |
| static void translog_file_init(TRANSLOG_FILE *file, uint32 number,
 | |
|                                my_bool is_sync)
 | |
| {
 | |
|   pagecache_file_set_null_hooks(&file->handler);
 | |
|   file->handler.post_read_hook= translog_page_validator;
 | |
|   file->handler.flush_log_callback= maria_flush_log_for_page_none;
 | |
|   file->handler.callback_data= (uchar*)file;
 | |
| 
 | |
|   file->number= number;
 | |
|   file->was_recovered= 0;
 | |
|   file->is_sync= is_sync;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Create and fill header of new file.
 | |
| 
 | |
|   @note the caller must call it right after it has increased
 | |
|    log_descriptor.horizon to the new file
 | |
|    (log_descriptor.horizon+= LSN_ONE_FILE)
 | |
| 
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_create_new_file()
 | |
| {
 | |
|   TRANSLOG_FILE *file= (TRANSLOG_FILE*)my_malloc(PSI_INSTRUMENT_ME, sizeof(TRANSLOG_FILE),
 | |
|                                                  MYF(0));
 | |
| 
 | |
|   TRANSLOG_FILE *old= get_current_logfile();
 | |
|   uint32 file_no= LSN_FILE_NO(log_descriptor.horizon);
 | |
|   DBUG_ENTER("translog_create_new_file");
 | |
| 
 | |
|   if (file == NULL)
 | |
|     goto error;
 | |
| 
 | |
|   /*
 | |
|     Writes max_lsn to the file header before finishing it (there is no need
 | |
|     to lock file header buffer because it is still unfinished file, so only
 | |
|     one thread can finish the file and nobody interested of LSN of current
 | |
|     (unfinished) file, because no one can purge it).
 | |
|   */
 | |
|   if (translog_max_lsn_to_header(old->handler.file, log_descriptor.max_lsn))
 | |
|     goto error;
 | |
| 
 | |
|   mysql_rwlock_wrlock(&log_descriptor.open_files_lock);
 | |
|   DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|               log_descriptor.open_files.elements);
 | |
|   DBUG_ASSERT(file_no == log_descriptor.max_file + 1);
 | |
|   if (allocate_dynamic(&log_descriptor.open_files,
 | |
|                        log_descriptor.max_file - log_descriptor.min_file + 2))
 | |
|     goto error_lock;
 | |
| 
 | |
|   /* this call just expand the array */
 | |
|   if (insert_dynamic(&log_descriptor.open_files, (uchar*)&file))
 | |
|     goto error_lock;
 | |
| 
 | |
|   if ((file->handler.file= create_logfile_by_number_no_cache(file_no)) == -1)
 | |
|     goto error_lock;
 | |
|   translog_file_init(file, file_no, 0);
 | |
| 
 | |
|   log_descriptor.max_file++;
 | |
|   {
 | |
|     char *start= (char*) dynamic_element(&log_descriptor.open_files, 0,
 | |
|                                          TRANSLOG_FILE**);
 | |
|     memmove(start + sizeof(TRANSLOG_FILE*), start,
 | |
|             sizeof(TRANSLOG_FILE*) *
 | |
|             (log_descriptor.max_file - log_descriptor.min_file + 1 - 1));
 | |
|   }
 | |
|   /* can't fail we because we expanded array */
 | |
|   set_dynamic(&log_descriptor.open_files, (uchar*)&file, 0);
 | |
|   DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|               log_descriptor.open_files.elements);
 | |
|   mysql_rwlock_unlock(&log_descriptor.open_files_lock);
 | |
| 
 | |
|   DBUG_PRINT("info", ("file_no: %lu", (ulong)file_no));
 | |
| 
 | |
|   if (translog_write_file_header())
 | |
|     goto error;
 | |
| 
 | |
|   if (ma_control_file_write_and_force(last_checkpoint_lsn, file_no,
 | |
|                                       max_trid_in_control_file,
 | |
|                                       recovery_failures))
 | |
|     goto error;
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
| error_lock:
 | |
|   mysql_rwlock_unlock(&log_descriptor.open_files_lock);
 | |
| error:
 | |
|   translog_stop_writing();
 | |
|   my_free(file);
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Locks the loghandler buffer.
 | |
| 
 | |
|   @param buffer          This buffer which should be locked
 | |
| 
 | |
|   @note See comment before buffer 'mutex' variable.
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static void translog_buffer_lock(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_buffer_lock");
 | |
|   DBUG_PRINT("enter",
 | |
|              ("Lock buffer #%u: %p", buffer->buffer_no,
 | |
|               buffer));
 | |
|   mysql_mutex_lock(&buffer->mutex);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Unlock the loghandler buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_buffer_unlock()
 | |
|     buffer               This buffer which should be unlocked
 | |
| */
 | |
| 
 | |
| static void translog_buffer_unlock(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_buffer_unlock");
 | |
|   DBUG_PRINT("enter", ("Unlock buffer... #%u (%p)",
 | |
|                        (uint) buffer->buffer_no, buffer));
 | |
| 
 | |
|   mysql_mutex_unlock(&buffer->mutex);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Write a header on the page
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_new_page_header()
 | |
|     horizon              Where to write the page
 | |
|     cursor               Where to write the page
 | |
| 
 | |
|   NOTE
 | |
|     - space for page header should be checked before
 | |
| */
 | |
| 
 | |
| static uchar translog_sector_random;
 | |
| 
 | |
| static void translog_new_page_header(TRANSLOG_ADDRESS *horizon,
 | |
|                                      struct st_buffer_cursor *cursor)
 | |
| {
 | |
|   uchar *ptr;
 | |
| 
 | |
|   DBUG_ENTER("translog_new_page_header");
 | |
|   DBUG_ASSERT(cursor->ptr);
 | |
| 
 | |
|   cursor->protected= 0;
 | |
| 
 | |
|   ptr= cursor->ptr;
 | |
|   /* Page number */
 | |
|   int3store(ptr, LSN_OFFSET(*horizon) / TRANSLOG_PAGE_SIZE);
 | |
|   ptr+= 3;
 | |
|   /* File number */
 | |
|   int3store(ptr, LSN_FILE_NO(*horizon));
 | |
|   ptr+= 3;
 | |
|   DBUG_ASSERT(TRANSLOG_PAGE_FLAGS == (ptr - cursor->ptr));
 | |
|   cursor->ptr[TRANSLOG_PAGE_FLAGS]= (uchar) log_descriptor.flags;
 | |
|   ptr++;
 | |
|   if (log_descriptor.flags & TRANSLOG_PAGE_CRC)
 | |
|   {
 | |
| #ifndef DBUG_OFF
 | |
|     DBUG_PRINT("info", ("write  0x11223344 CRC to " LSN_FMT,
 | |
|                         LSN_IN_PARTS(*horizon)));
 | |
|     /* This will be overwritten by real CRC; This is just for debugging */
 | |
|     int4store(ptr, 0x11223344);
 | |
| #endif
 | |
|     /* CRC will be put when page is finished */
 | |
|     ptr+= CRC_SIZE;
 | |
|   }
 | |
|   if (log_descriptor.flags & TRANSLOG_SECTOR_PROTECTION)
 | |
|   {
 | |
|     /*
 | |
|       translog_sector_randmo works like "random" values producer because
 | |
|       it is enough to have such "random" for this purpose and it will
 | |
|       not interfere with higher level pseudo random value generator
 | |
|     */
 | |
|     ptr[0]= translog_sector_random++;
 | |
|     ptr+= TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|   }
 | |
|   {
 | |
|     size_t len= (ptr - cursor->ptr);
 | |
|     (*horizon)+= len; /* increasing the offset part of the address */
 | |
|     cursor->current_page_fill= (uint16)len;
 | |
|     if (!cursor->chaser)
 | |
|       cursor->buffer->size+= (translog_size_t)len;
 | |
|   }
 | |
|   cursor->ptr= ptr;
 | |
|   DBUG_PRINT("info", ("NewP buffer #%u: %p  chaser: %d  Size: %lu (%lu)  "
 | |
|                       "Horizon: " LSN_FMT,
 | |
|                       (uint) cursor->buffer->buffer_no, cursor->buffer,
 | |
|                       cursor->chaser, (ulong) cursor->buffer->size,
 | |
|                       (ulong) (cursor->ptr - cursor->buffer->buffer),
 | |
|                       LSN_IN_PARTS(*horizon)));
 | |
|   translog_check_cursor(cursor);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Put sector protection on the page image
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_put_sector_protection()
 | |
|     page                 reference on the page content
 | |
|     cursor               cursor of the buffer
 | |
| 
 | |
|   NOTES
 | |
|     We put a sector protection on all following sectors on the page,
 | |
|     except the first sector that is protected by page header.
 | |
| */
 | |
| 
 | |
| static void translog_put_sector_protection(uchar *page,
 | |
|                                            struct st_buffer_cursor *cursor)
 | |
| {
 | |
|   uchar *table= page + log_descriptor.page_overhead -
 | |
|     TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|   uint i, offset;
 | |
|   uint16 last_protected_sector= ((cursor->previous_offset - 1) /
 | |
|                                  DISK_DRIVE_SECTOR_SIZE);
 | |
|   uint16 start_sector= cursor->previous_offset / DISK_DRIVE_SECTOR_SIZE;
 | |
|   uint8 value= table[0] + cursor->write_counter;
 | |
|   DBUG_ENTER("translog_put_sector_protection");
 | |
| 
 | |
|   if (start_sector == 0)
 | |
|   {
 | |
|     /* First sector is protected by file & page numbers in the page header. */
 | |
|     start_sector= 1;
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("enter", ("Write counter:%u  value:%u  offset:%u, "
 | |
|                        "last protected:%u  start sector:%u",
 | |
|                        (uint) cursor->write_counter,
 | |
|                        (uint) value,
 | |
|                        (uint) cursor->previous_offset,
 | |
|                        (uint) last_protected_sector, (uint) start_sector));
 | |
|   if (last_protected_sector == start_sector)
 | |
|   {
 | |
|     i= last_protected_sector;
 | |
|     offset= last_protected_sector * DISK_DRIVE_SECTOR_SIZE;
 | |
|     /* restore data, because we modified sector which was protected */
 | |
|     if (offset < cursor->previous_offset)
 | |
|       page[offset]= table[i];
 | |
|   }
 | |
|   for (i= start_sector, offset= start_sector * DISK_DRIVE_SECTOR_SIZE;
 | |
|        i < TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|        i++, (offset+= DISK_DRIVE_SECTOR_SIZE))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("sector:%u  offset:%u  data 0x%x",
 | |
|                         i, offset, (uint) page[offset]));
 | |
|     table[i]= page[offset];
 | |
|     page[offset]= value;
 | |
|     DBUG_PRINT("info", ("sector:%u  offset:%u  data 0x%x",
 | |
|                         i, offset, (uint) page[offset]));
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Calculate CRC32 of given area
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_crc()
 | |
|     area                 Pointer of the area beginning
 | |
|     length               The Area length
 | |
| 
 | |
|   RETURN
 | |
|     CRC32
 | |
| */
 | |
| 
 | |
| static uint32 translog_crc(uchar *area, uint length)
 | |
| {
 | |
|   DBUG_ENTER("translog_crc");
 | |
|   DBUG_RETURN(my_checksum(0L, area, length));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Finish current page with zeros
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_finish_page()
 | |
|     horizon              \ horizon & buffer pointers
 | |
|     cursor               /
 | |
| */
 | |
| 
 | |
| static void translog_finish_page(TRANSLOG_ADDRESS *horizon,
 | |
|                                  struct st_buffer_cursor *cursor)
 | |
| {
 | |
|   uint16 left= TRANSLOG_PAGE_SIZE - cursor->current_page_fill;
 | |
|   uchar *page= cursor->ptr - cursor->current_page_fill;
 | |
|   DBUG_ENTER("translog_finish_page");
 | |
|   DBUG_PRINT("enter", ("Buffer: #%u %p  "
 | |
|                        "Buffer addr: " LSN_FMT "  "
 | |
|                        "Page addr: " LSN_FMT "  "
 | |
|                        "size:%u (%u)  Pg:%u  left:%u",
 | |
|                        (uint) cursor->buffer_no, cursor->buffer,
 | |
|                        LSN_IN_PARTS(cursor->buffer->offset),
 | |
|                        (uint)LSN_FILE_NO(*horizon),
 | |
|                        (uint)(LSN_OFFSET(*horizon) -
 | |
|                                 cursor->current_page_fill),
 | |
|                        (uint) cursor->buffer->size,
 | |
|                        (uint) (cursor->ptr -cursor->buffer->buffer),
 | |
|                        (uint) cursor->current_page_fill, (uint) left));
 | |
|   DBUG_ASSERT(LSN_FILE_NO(*horizon) == LSN_FILE_NO(cursor->buffer->offset)
 | |
|               || translog_status == TRANSLOG_UNINITED);
 | |
|   if ((LSN_FILE_NO(*horizon) != LSN_FILE_NO(cursor->buffer->offset)))
 | |
|     DBUG_VOID_RETURN; // everything wrong do not write to awoid more problems
 | |
|   translog_check_cursor(cursor);
 | |
|   if (cursor->protected)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Already protected and finished"));
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   cursor->protected= 1;
 | |
| 
 | |
|   DBUG_ASSERT(left < TRANSLOG_PAGE_SIZE);
 | |
|   if (left != 0)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("left: %u", (uint) left));
 | |
|     memset(cursor->ptr, TRANSLOG_FILLER, left);
 | |
|     cursor->ptr+= left;
 | |
|     (*horizon)+= left; /* offset increasing */
 | |
|     if (!cursor->chaser)
 | |
|       cursor->buffer->size+= left;
 | |
|     /* We are finishing the page so reset the counter */
 | |
|     cursor->current_page_fill= 0;
 | |
|     DBUG_PRINT("info", ("Finish Page buffer #%u: %p "
 | |
|                         "chaser: %d  Size: %lu (%lu)",
 | |
|                         (uint) cursor->buffer->buffer_no,
 | |
|                         cursor->buffer, cursor->chaser,
 | |
|                         (ulong) cursor->buffer->size,
 | |
|                         (ulong) (cursor->ptr - cursor->buffer->buffer)));
 | |
|     translog_check_cursor(cursor);
 | |
|   }
 | |
|   /*
 | |
|     When we are finishing the page other thread might not finish the page
 | |
|     header yet (in case if we started from the middle of the page) so we
 | |
|     have to read log_descriptor.flags but not the flags from the page.
 | |
|   */
 | |
|   if (log_descriptor.flags & TRANSLOG_SECTOR_PROTECTION)
 | |
|   {
 | |
|     translog_put_sector_protection(page, cursor);
 | |
|     DBUG_PRINT("info", ("drop write_counter"));
 | |
|     cursor->write_counter= 0;
 | |
|     cursor->previous_offset= 0;
 | |
|   }
 | |
|   if (log_descriptor.flags & TRANSLOG_PAGE_CRC)
 | |
|   {
 | |
|     uint32 crc= translog_crc(page + log_descriptor.page_overhead,
 | |
|                              TRANSLOG_PAGE_SIZE -
 | |
|                              log_descriptor.page_overhead);
 | |
|     DBUG_PRINT("info", ("CRC: %lx", (ulong) crc));
 | |
|     /* We have page number, file number and flag before crc */
 | |
|     int4store(page + 3 + 3 + 1, crc);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Wait until all threads have finished closing this buffer.
 | |
| 
 | |
|   @param buffer          This buffer should be check
 | |
| */
 | |
| 
 | |
| static void translog_wait_for_closing(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_wait_for_closing");
 | |
|   DBUG_PRINT("enter", ("Buffer #%u %p  copies in progress: %u  "
 | |
|                        "is closing %u  File: %d  size: %lu",
 | |
|                        (uint) buffer->buffer_no, buffer,
 | |
|                        (uint) buffer->copy_to_buffer_in_progress,
 | |
|                        (uint) buffer->is_closing_buffer,
 | |
|                        (buffer->file ? buffer->file->handler.file : -1),
 | |
|                        (ulong) buffer->size));
 | |
|   translog_buffer_lock_assert_owner(buffer);
 | |
| 
 | |
|   while (buffer->is_closing_buffer)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("wait for writers... buffer: #%u %p",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|     DBUG_ASSERT(buffer->file != NULL);
 | |
|     mysql_cond_wait(&buffer->waiting_filling_buffer, &buffer->mutex);
 | |
|     DBUG_PRINT("info", ("wait for writers done buffer: #%u %p",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|   }
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Wait until all threads have finished filling this buffer.
 | |
| 
 | |
|   @param buffer          This buffer should be check
 | |
| */
 | |
| 
 | |
| static void translog_wait_for_writers(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_wait_for_writers");
 | |
|   DBUG_PRINT("enter", ("Buffer #%u %p copies in progress: %u  "
 | |
|                        "is closing %u  File: %d  size: %lu",
 | |
|                        (uint) buffer->buffer_no, buffer,
 | |
|                        (uint) buffer->copy_to_buffer_in_progress,
 | |
|                        (uint) buffer->is_closing_buffer,
 | |
|                        (buffer->file ? buffer->file->handler.file : -1),
 | |
|                        (ulong) buffer->size));
 | |
|   translog_buffer_lock_assert_owner(buffer);
 | |
| 
 | |
|   while (buffer->copy_to_buffer_in_progress)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("wait for writers... buffer: #%u  %p",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|     DBUG_ASSERT(buffer->file != NULL);
 | |
|     mysql_cond_wait(&buffer->waiting_filling_buffer, &buffer->mutex);
 | |
|     DBUG_PRINT("info", ("wait for writers done buffer: #%u  %p",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|   }
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| 
 | |
|   Wait for buffer to become free
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_wait_for_buffer_free()
 | |
|     buffer               The buffer we are waiting for
 | |
| 
 | |
|   NOTE
 | |
|     - this buffer should be locked
 | |
| */
 | |
| 
 | |
| static void translog_wait_for_buffer_free(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   TRANSLOG_ADDRESS offset= buffer->offset;
 | |
|   TRANSLOG_FILE *file= buffer->file;
 | |
|   uint8 ver= buffer->ver;
 | |
|   DBUG_ENTER("translog_wait_for_buffer_free");
 | |
|   DBUG_PRINT("enter", ("Buffer #%u %p  copies in progress: %u  "
 | |
|                        "is closing %u  File: %d  size: %lu",
 | |
|                        (uint) buffer->buffer_no, buffer,
 | |
|                        (uint) buffer->copy_to_buffer_in_progress,
 | |
|                        (uint) buffer->is_closing_buffer,
 | |
|                        (buffer->file ? buffer->file->handler.file : -1),
 | |
|                        (ulong) buffer->size));
 | |
| 
 | |
|   translog_wait_for_writers(buffer);
 | |
| 
 | |
|   if (offset != buffer->offset || file != buffer->file || ver != buffer->ver)
 | |
|     DBUG_VOID_RETURN; /* the buffer if already freed */
 | |
| 
 | |
|   while (buffer->file != NULL)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("wait for writers... buffer: #%u  %p",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|     mysql_cond_wait(&buffer->waiting_filling_buffer, &buffer->mutex);
 | |
|     DBUG_PRINT("info", ("wait for writers done. buffer: #%u  %p",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|   }
 | |
|   DBUG_ASSERT(buffer->copy_to_buffer_in_progress == 0);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Initialize the cursor for a buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_cursor_init()
 | |
|     buffer               The buffer
 | |
|     cursor               It's cursor
 | |
|     buffer_no            Number of buffer
 | |
| */
 | |
| 
 | |
| static void translog_cursor_init(struct st_buffer_cursor *cursor,
 | |
|                                  struct st_translog_buffer *buffer,
 | |
|                                  uint8 buffer_no)
 | |
| {
 | |
|   DBUG_ENTER("translog_cursor_init");
 | |
|   cursor->ptr= buffer->buffer;
 | |
|   cursor->buffer= buffer;
 | |
|   cursor->buffer_no= buffer_no;
 | |
|   cursor->current_page_fill= 0;
 | |
|   cursor->chaser= (cursor != &log_descriptor.bc);
 | |
|   cursor->write_counter= 0;
 | |
|   cursor->previous_offset= 0;
 | |
|   cursor->protected= 0;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Initialize buffer for the current file, and a cursor for this buffer.
 | |
| 
 | |
|   @param buffer          The buffer
 | |
|   @param cursor          It's cursor
 | |
|   @param buffer_no       Number of buffer
 | |
| */
 | |
| 
 | |
| static void translog_start_buffer(struct st_translog_buffer *buffer,
 | |
|                                   struct st_buffer_cursor *cursor,
 | |
|                                   uint buffer_no)
 | |
| {
 | |
|   DBUG_ENTER("translog_start_buffer");
 | |
|   DBUG_PRINT("enter",
 | |
|              ("Assign buffer: #%u (%p) offset: 0x%x(%u)",
 | |
|               (uint) buffer->buffer_no, buffer,
 | |
|               (uint) LSN_OFFSET(log_descriptor.horizon),
 | |
|               (uint) LSN_OFFSET(log_descriptor.horizon)));
 | |
|   DBUG_ASSERT(buffer_no == buffer->buffer_no);
 | |
|   buffer->pre_force_close_horizon=
 | |
|     buffer->prev_last_lsn= buffer->last_lsn= LSN_IMPOSSIBLE;
 | |
|   DBUG_PRINT("info", ("last_lsn and prev_last_lsn set to 0  buffer: %p",
 | |
|                       buffer));
 | |
|   buffer->offset= log_descriptor.horizon;
 | |
|   buffer->next_buffer_offset= LSN_IMPOSSIBLE;
 | |
|   buffer->file= get_current_logfile();
 | |
|   buffer->overlay= 0;
 | |
|   buffer->size= 0;
 | |
|   buffer->skipped_data= 0;
 | |
|   translog_cursor_init(cursor, buffer, buffer_no);
 | |
|   DBUG_PRINT("info", ("file: #%ld (%d)  init cursor #%u: %p "
 | |
|                       "chaser: %d  Size: %lu (%lu)",
 | |
|                       (long) (buffer->file ? buffer->file->number : 0),
 | |
|                       (buffer->file ? buffer->file->handler.file : -1),
 | |
|                       (uint) cursor->buffer->buffer_no, cursor->buffer,
 | |
|                       cursor->chaser, (ulong) cursor->buffer->size,
 | |
|                       (ulong) (cursor->ptr - cursor->buffer->buffer)));
 | |
|   translog_check_cursor(cursor);
 | |
|   mysql_mutex_lock(&log_descriptor.dirty_buffer_mask_lock);
 | |
|   log_descriptor.dirty_buffer_mask|= (1 << buffer->buffer_no);
 | |
|   mysql_mutex_unlock(&log_descriptor.dirty_buffer_mask_lock);
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Switch to the next buffer in a chain.
 | |
| 
 | |
|   @param horizon         \ Pointers on current position in file and buffer
 | |
|   @param cursor          /
 | |
|   @param new_file        Also start new file
 | |
| 
 | |
|   @note
 | |
|    - loghandler should be locked
 | |
|    - after return new and old buffer still are locked
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_buffer_next(TRANSLOG_ADDRESS *horizon,
 | |
|                                     struct st_buffer_cursor *cursor,
 | |
|                                     my_bool new_file)
 | |
| {
 | |
|   uint old_buffer_no= cursor->buffer_no;
 | |
|   uint new_buffer_no= (old_buffer_no + 1) % TRANSLOG_BUFFERS_NO;
 | |
|   struct st_translog_buffer *new_buffer= log_descriptor.buffers + new_buffer_no;
 | |
|   my_bool chasing= cursor->chaser;
 | |
|   DBUG_ENTER("translog_buffer_next");
 | |
| 
 | |
|   DBUG_PRINT("info", ("horizon: " LSN_FMT "  chasing: %d",
 | |
|                       LSN_IN_PARTS(log_descriptor.horizon), chasing));
 | |
| 
 | |
|   DBUG_ASSERT(cmp_translog_addr(log_descriptor.horizon, *horizon) >= 0);
 | |
| 
 | |
|   translog_finish_page(horizon, cursor);
 | |
| 
 | |
|   if (!chasing)
 | |
|   {
 | |
|     translog_buffer_lock(new_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|     {
 | |
|       TRANSLOG_ADDRESS offset= new_buffer->offset;
 | |
|       TRANSLOG_FILE *file= new_buffer->file;
 | |
|       uint8 ver= new_buffer->ver;
 | |
|       translog_lock_assert_owner();
 | |
| #endif
 | |
|       translog_wait_for_buffer_free(new_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|       /* We keep the handler locked so nobody can start this new buffer */
 | |
|       DBUG_ASSERT(offset == new_buffer->offset && new_buffer->file == NULL &&
 | |
|                   (file == NULL ? ver : (uint8)(ver + 1)) == new_buffer->ver);
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
|   else
 | |
|     DBUG_ASSERT(new_buffer->file != NULL);
 | |
| 
 | |
|   if (new_file)
 | |
|   {
 | |
|     /* move the horizon to the next file and its header page */
 | |
|     (*horizon)+= LSN_ONE_FILE;
 | |
|     (*horizon)= LSN_REPLACE_OFFSET(*horizon, TRANSLOG_PAGE_SIZE);
 | |
|     if (!chasing && translog_create_new_file())
 | |
|     {
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* prepare next page */
 | |
|   if (chasing)
 | |
|     translog_cursor_init(cursor, new_buffer, new_buffer_no);
 | |
|   else
 | |
|   {
 | |
|     translog_lock_assert_owner();
 | |
|     translog_start_buffer(new_buffer, cursor, new_buffer_no);
 | |
|     new_buffer->prev_buffer_offset=
 | |
|       log_descriptor.buffers[old_buffer_no].offset;
 | |
|     new_buffer->prev_last_lsn=
 | |
|       BUFFER_MAX_LSN(log_descriptor.buffers + old_buffer_no);
 | |
|   }
 | |
|   log_descriptor.buffers[old_buffer_no].next_buffer_offset= new_buffer->offset;
 | |
|   DBUG_PRINT("info", ("prev_last_lsn set to " LSN_FMT "  buffer:%p",
 | |
|                       LSN_IN_PARTS(new_buffer->prev_last_lsn),
 | |
|                       new_buffer));
 | |
|   translog_new_page_header(horizon, cursor);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Sets max LSN sent to file, and address from which data is only in the buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_set_sent_to_disk()
 | |
|     buffer               buffer which we have sent to disk
 | |
| 
 | |
|   TODO: use atomic operations if possible (64bit architectures?)
 | |
| */
 | |
| 
 | |
| static void translog_set_sent_to_disk(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   LSN lsn= buffer->last_lsn;
 | |
|   TRANSLOG_ADDRESS in_buffers= buffer->next_buffer_offset;
 | |
| 
 | |
|   DBUG_ENTER("translog_set_sent_to_disk");
 | |
|   mysql_mutex_lock(&log_descriptor.sent_to_disk_lock);
 | |
|   DBUG_PRINT("enter", ("lsn: " LSN_FMT " in_buffers: " LSN_FMT "  "
 | |
|                        "in_buffers_only: " LSN_FMT "  start: " LSN_FMT "  "
 | |
|                        "sent_to_disk: " LSN_FMT,
 | |
|                        LSN_IN_PARTS(lsn),
 | |
|                        LSN_IN_PARTS(in_buffers),
 | |
|                        LSN_IN_PARTS(log_descriptor.log_start),
 | |
|                        LSN_IN_PARTS(log_descriptor.in_buffers_only),
 | |
|                        LSN_IN_PARTS(log_descriptor.sent_to_disk)));
 | |
|   /*
 | |
|     We write sequentially (first part of following assert) but we rewrite
 | |
|     the same page in case we started mysql and shut it down immediately
 | |
|     (second part of the following assert)
 | |
|   */
 | |
|   DBUG_ASSERT(cmp_translog_addr(lsn, log_descriptor.sent_to_disk) >= 0 ||
 | |
|               cmp_translog_addr(lsn, log_descriptor.log_start) < 0);
 | |
|   log_descriptor.sent_to_disk= lsn;
 | |
|   /* LSN_IMPOSSIBLE == 0 => it will work for very first time */
 | |
|   if (cmp_translog_addr(in_buffers, log_descriptor.in_buffers_only) > 0)
 | |
|   {
 | |
|     log_descriptor.in_buffers_only= in_buffers;
 | |
|     DBUG_PRINT("info", ("set new in_buffers_only"));
 | |
|   }
 | |
|   mysql_mutex_unlock(&log_descriptor.sent_to_disk_lock);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Sets address from which data is only in the buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_set_only_in_buffers()
 | |
|     lsn                  LSN to assign
 | |
|     in_buffers           to assign to in_buffers_only
 | |
| */
 | |
| 
 | |
| static void translog_set_only_in_buffers(TRANSLOG_ADDRESS in_buffers)
 | |
| {
 | |
|   DBUG_ENTER("translog_set_only_in_buffers");
 | |
|   mysql_mutex_lock(&log_descriptor.sent_to_disk_lock);
 | |
|   DBUG_PRINT("enter", ("in_buffers: " LSN_FMT "  "
 | |
|                        "in_buffers_only: " LSN_FMT,
 | |
|                        LSN_IN_PARTS(in_buffers),
 | |
|                        LSN_IN_PARTS(log_descriptor.in_buffers_only)));
 | |
|   /* LSN_IMPOSSIBLE == 0 => it will work for very first time */
 | |
|   if (cmp_translog_addr(in_buffers, log_descriptor.in_buffers_only) > 0)
 | |
|   {
 | |
|     if (translog_status != TRANSLOG_OK)
 | |
|       goto end;
 | |
|     log_descriptor.in_buffers_only= in_buffers;
 | |
|     DBUG_PRINT("info", ("set new in_buffers_only"));
 | |
|   }
 | |
| end:
 | |
|   mysql_mutex_unlock(&log_descriptor.sent_to_disk_lock);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Gets address from which data is only in the buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_only_in_buffers()
 | |
| 
 | |
|   RETURN
 | |
|     address from which data is only in the buffer
 | |
| */
 | |
| 
 | |
| static TRANSLOG_ADDRESS translog_only_in_buffers()
 | |
| {
 | |
|   register TRANSLOG_ADDRESS addr;
 | |
|   DBUG_ENTER("translog_only_in_buffers");
 | |
|   mysql_mutex_lock(&log_descriptor.sent_to_disk_lock);
 | |
|   addr= log_descriptor.in_buffers_only;
 | |
|   mysql_mutex_unlock(&log_descriptor.sent_to_disk_lock);
 | |
|   DBUG_RETURN(addr);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get max LSN sent to file
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_sent_to_disk()
 | |
| 
 | |
|   RETURN
 | |
|     max LSN send to file
 | |
| */
 | |
| 
 | |
| static LSN translog_get_sent_to_disk()
 | |
| {
 | |
|   register LSN lsn;
 | |
|   DBUG_ENTER("translog_get_sent_to_disk");
 | |
|   mysql_mutex_lock(&log_descriptor.sent_to_disk_lock);
 | |
|   lsn= log_descriptor.sent_to_disk;
 | |
|   DBUG_PRINT("info", ("sent to disk up to " LSN_FMT, LSN_IN_PARTS(lsn)));
 | |
|   mysql_mutex_unlock(&log_descriptor.sent_to_disk_lock);
 | |
|   DBUG_RETURN(lsn);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get first chunk address on the given page
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_first_chunk_offset()
 | |
|     page                 The page where to find first chunk
 | |
| 
 | |
|   RETURN
 | |
|     first chunk offset
 | |
| */
 | |
| 
 | |
| static my_bool translog_get_first_chunk_offset(uchar *page)
 | |
| {
 | |
|   DBUG_ENTER("translog_get_first_chunk_offset");
 | |
|   DBUG_ASSERT(page[TRANSLOG_PAGE_FLAGS] < TRANSLOG_FLAGS_NUM);
 | |
|   DBUG_RETURN(page_overhead[page[TRANSLOG_PAGE_FLAGS]]);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Write coded length of record
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_variable_record_1group_code_len
 | |
|     dst                  Destination buffer pointer
 | |
|     length               Length which should be coded
 | |
|     header_len           Calculated total header length
 | |
| */
 | |
| 
 | |
| static void
 | |
| translog_write_variable_record_1group_code_len(uchar *dst,
 | |
|                                                translog_size_t length,
 | |
|                                                uint16 header_len)
 | |
| {
 | |
|   switch (header_len) {
 | |
|   case 6:                                      /* (5 + 1) */
 | |
|     DBUG_ASSERT(length <= 250);
 | |
|     *dst= (uint8) length;
 | |
|     return;
 | |
|   case 8:                                      /* (5 + 3) */
 | |
|     DBUG_ASSERT(length <= 0xFFFF);
 | |
|     *dst= 251;
 | |
|     int2store(dst + 1, length);
 | |
|     return;
 | |
|   case 9:                                      /* (5 + 4) */
 | |
|     DBUG_ASSERT(length <= (ulong) 0xFFFFFF);
 | |
|     *dst= 252;
 | |
|     int3store(dst + 1, length);
 | |
|     return;
 | |
|   case 10:                                     /* (5 + 5) */
 | |
|     *dst= 253;
 | |
|     int4store(dst + 1, length);
 | |
|     return;
 | |
|   default:
 | |
|     DBUG_ASSERT(0);
 | |
|   }
 | |
|   return;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Decode record data length and advance given pointer to the next field
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_variable_record_1group_decode_len()
 | |
|     src                  The pointer to the pointer to the length beginning
 | |
| 
 | |
|   RETURN
 | |
|     decoded length
 | |
| */
 | |
| 
 | |
| static translog_size_t translog_variable_record_1group_decode_len(uchar **src)
 | |
| {
 | |
|   uint8 first= (uint8) (**src);
 | |
|   switch (first) {
 | |
|   case 251:
 | |
|     (*src)+= 3;
 | |
|     return (uint2korr((*src) - 2));
 | |
|   case 252:
 | |
|     (*src)+= 4;
 | |
|     return (uint3korr((*src) - 3));
 | |
|   case 253:
 | |
|     (*src)+= 5;
 | |
|     return (uint4korr((*src) - 4));
 | |
|   case 254:
 | |
|   case 255:
 | |
|     DBUG_ASSERT(0);                             /* reserved for future use */
 | |
|     return (0);
 | |
|   default:
 | |
|     (*src)++;
 | |
|     return (first);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get total length of this chunk (not only body)
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_total_chunk_length()
 | |
|     page                 The page where chunk placed
 | |
|     offset               Offset of the chunk on this place
 | |
| 
 | |
|   RETURN
 | |
|     total length of the chunk
 | |
| */
 | |
| 
 | |
| static uint16 translog_get_total_chunk_length(uchar *page, uint16 offset)
 | |
| {
 | |
|   DBUG_ENTER("translog_get_total_chunk_length");
 | |
|   switch (page[offset] & TRANSLOG_CHUNK_TYPE) {
 | |
|   case TRANSLOG_CHUNK_LSN:
 | |
|   {
 | |
|     /* 0 chunk referred as LSN (head or tail) */
 | |
|     translog_size_t rec_len;
 | |
|     uchar *start= page + offset;
 | |
|     uchar *ptr= start + 1 + 2; /* chunk type and short trid */
 | |
|     uint16 chunk_len, header_len, page_rest;
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_LSN"));
 | |
|     rec_len= translog_variable_record_1group_decode_len(&ptr);
 | |
|     chunk_len= uint2korr(ptr);
 | |
|     header_len= (uint16) (ptr -start) + 2;
 | |
|     DBUG_PRINT("info", ("rec len: %lu  chunk len: %u  header len: %u",
 | |
|                         (ulong) rec_len, (uint) chunk_len, (uint) header_len));
 | |
|     if (chunk_len)
 | |
|     {
 | |
|       DBUG_PRINT("info", ("chunk len: %u + %u = %u",
 | |
|                           (uint) header_len, (uint) chunk_len,
 | |
|                           (uint) (chunk_len + header_len)));
 | |
|       DBUG_RETURN(chunk_len + header_len);
 | |
|     }
 | |
|     page_rest= TRANSLOG_PAGE_SIZE - offset;
 | |
|     DBUG_PRINT("info", ("page_rest %u", (uint) page_rest));
 | |
|     if (rec_len + header_len < page_rest)
 | |
|       DBUG_RETURN(rec_len + header_len);
 | |
|     DBUG_RETURN(page_rest);
 | |
|   }
 | |
|   case TRANSLOG_CHUNK_FIXED:
 | |
|   {
 | |
|     uchar *ptr;
 | |
|     uint type= page[offset] & TRANSLOG_REC_TYPE;
 | |
|     uint length;
 | |
|     int i;
 | |
|     /* 1 (pseudo)fixed record (also LSN) */
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_FIXED"));
 | |
|     DBUG_ASSERT(log_record_type_descriptor[type].rclass ==
 | |
|                 LOGRECTYPE_FIXEDLENGTH ||
 | |
|                 log_record_type_descriptor[type].rclass ==
 | |
|                 LOGRECTYPE_PSEUDOFIXEDLENGTH);
 | |
|     if (log_record_type_descriptor[type].rclass == LOGRECTYPE_FIXEDLENGTH)
 | |
|     {
 | |
|       DBUG_PRINT("info",
 | |
|                  ("Fixed length: %u",
 | |
|                   (uint) (log_record_type_descriptor[type].fixed_length + 3)));
 | |
|       DBUG_RETURN(log_record_type_descriptor[type].fixed_length + 3);
 | |
|     }
 | |
| 
 | |
|     ptr= page + offset + 3;            /* first compressed LSN */
 | |
|     length= log_record_type_descriptor[type].fixed_length + 3;
 | |
|     for (i= 0; i < log_record_type_descriptor[type].compressed_LSN; i++)
 | |
|     {
 | |
|       /* first 2 bits is length - 2 */
 | |
|       uint len= (((uint8) (*ptr)) >> 6) + 2;
 | |
|       if (ptr[0] == 0 && ((uint8) ptr[1]) == 1)
 | |
|         len+= LSN_STORE_SIZE; /* case of full LSN storing */
 | |
|       ptr+= len;
 | |
|       /* subtract saved bytes */
 | |
|       length-= (LSN_STORE_SIZE - len);
 | |
|     }
 | |
|     DBUG_PRINT("info", ("Pseudo-fixed length: %u", length));
 | |
|     DBUG_RETURN(length);
 | |
|   }
 | |
|   case TRANSLOG_CHUNK_NOHDR:
 | |
|     /* 2 no header chunk (till page end) */
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_NOHDR  length: %u",
 | |
|                         (uint) (TRANSLOG_PAGE_SIZE - offset)));
 | |
|     DBUG_RETURN(TRANSLOG_PAGE_SIZE - offset);
 | |
|   case TRANSLOG_CHUNK_LNGTH:                   /* 3 chunk with chunk length */
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_LNGTH"));
 | |
|     DBUG_ASSERT(TRANSLOG_PAGE_SIZE - offset >= 3);
 | |
|     DBUG_PRINT("info", ("length: %u", uint2korr(page + offset + 1) + 3));
 | |
|     DBUG_RETURN(uint2korr(page + offset + 1) + 3);
 | |
|   default:
 | |
|     DBUG_ASSERT(0);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|   @brief Waits previous buffer flush finish
 | |
| 
 | |
|   @param buffer          buffer for check
 | |
| 
 | |
|   @retval 0 previous buffer flushed and this thread have to flush this one
 | |
|   @retval 1 previous buffer flushed and this buffer flushed by other thread too
 | |
| */
 | |
| 
 | |
| my_bool translog_prev_buffer_flush_wait(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   TRANSLOG_ADDRESS offset= buffer->offset;
 | |
|   TRANSLOG_FILE *file= buffer->file;
 | |
|   uint8 ver= buffer->ver;
 | |
|   DBUG_ENTER("translog_prev_buffer_flush_wait");
 | |
|   DBUG_PRINT("enter", ("buffer: %p  #%u  offset: " LSN_FMT "  "
 | |
|                        "prev sent: " LSN_FMT " prev offset: " LSN_FMT,
 | |
|                        buffer, (uint) buffer->buffer_no,
 | |
|                        LSN_IN_PARTS(buffer->offset),
 | |
|                        LSN_IN_PARTS(buffer->prev_sent_to_disk),
 | |
|                        LSN_IN_PARTS(buffer->prev_buffer_offset)));
 | |
|   translog_buffer_lock_assert_owner(buffer);
 | |
|   if (buffer->prev_buffer_offset != buffer->prev_sent_to_disk)
 | |
|   {
 | |
|     do {
 | |
|       mysql_cond_wait(&buffer->prev_sent_to_disk_cond, &buffer->mutex);
 | |
|       if (buffer->file != file || buffer->offset != offset ||
 | |
|           buffer->ver != ver)
 | |
|         DBUG_RETURN(1); /* some the thread flushed the buffer already */
 | |
|     } while(buffer->prev_buffer_offset != buffer->prev_sent_to_disk);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Flush given buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_buffer_flush()
 | |
|     buffer               This buffer should be flushed
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_buffer_flush(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   uint32 i, pg;
 | |
|   TRANSLOG_ADDRESS offset= buffer->offset;
 | |
|   TRANSLOG_FILE *file= buffer->file;
 | |
|   uint8 ver= buffer->ver;
 | |
|   uint skipped_data;
 | |
|   DBUG_ENTER("translog_buffer_flush");
 | |
|   DBUG_PRINT("enter",
 | |
|              ("Buffer: #%u %p file: %d  offset: " LSN_FMT "  size: %lu",
 | |
|               (uint) buffer->buffer_no, buffer,
 | |
|               buffer->file->handler.file,
 | |
|               LSN_IN_PARTS(buffer->offset),
 | |
|               (ulong) buffer->size));
 | |
|   translog_buffer_lock_assert_owner(buffer);
 | |
| 
 | |
|   if (buffer->file == NULL)
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   translog_wait_for_writers(buffer);
 | |
| 
 | |
|   if (buffer->file != file || buffer->offset != offset || buffer->ver != ver)
 | |
|     DBUG_RETURN(0); /* some the thread flushed the buffer already */
 | |
| 
 | |
|   if (buffer->is_closing_buffer)
 | |
|   {
 | |
|     /* some other flush in progress */
 | |
|     translog_wait_for_closing(buffer);
 | |
|     if (buffer->file != file || buffer->offset != offset || buffer->ver != ver)
 | |
|       DBUG_RETURN(0); /* some the thread flushed the buffer already */
 | |
|   }
 | |
| 
 | |
|   if (buffer->overlay && translog_prev_buffer_flush_wait(buffer))
 | |
|     DBUG_RETURN(0); /* some the thread flushed the buffer already */
 | |
| 
 | |
|   /*
 | |
|     Send page by page in the pagecache what we are going to write on the
 | |
|     disk
 | |
|   */
 | |
|   file= buffer->file;
 | |
|   skipped_data= buffer->skipped_data;
 | |
|   DBUG_ASSERT(skipped_data < TRANSLOG_PAGE_SIZE);
 | |
|   for (i= 0, pg= LSN_OFFSET(buffer->offset) / TRANSLOG_PAGE_SIZE;
 | |
|        i < buffer->size;
 | |
|        i+= TRANSLOG_PAGE_SIZE, pg++)
 | |
|   {
 | |
| #ifdef DBUG_TRACE
 | |
|     TRANSLOG_ADDRESS addr= (buffer->offset + i);
 | |
| #endif
 | |
|     DBUG_PRINT("info", ("send log form %lu till %lu  address: " LSN_FMT "  "
 | |
|                         "page #: %lu  buffer size: %lu  buffer: %p",
 | |
|                         (ulong) i, (ulong) (i + TRANSLOG_PAGE_SIZE),
 | |
|                         LSN_IN_PARTS(addr), (ulong) pg, (ulong) buffer->size,
 | |
|                         buffer));
 | |
|     DBUG_ASSERT(log_descriptor.pagecache->block_size == TRANSLOG_PAGE_SIZE);
 | |
|     DBUG_ASSERT(i + TRANSLOG_PAGE_SIZE <= buffer->size);
 | |
|     if (translog_status != TRANSLOG_OK && translog_status != TRANSLOG_SHUTDOWN)
 | |
|       DBUG_RETURN(1);
 | |
|     if (pagecache_write_part(log_descriptor.pagecache,
 | |
|                         &file->handler, pg, 3,
 | |
|                         buffer->buffer + i,
 | |
|                         PAGECACHE_PLAIN_PAGE,
 | |
|                         PAGECACHE_LOCK_LEFT_UNLOCKED,
 | |
|                         PAGECACHE_PIN_LEFT_UNPINNED,
 | |
|                         PAGECACHE_WRITE_DONE, 0,
 | |
|                         LSN_IMPOSSIBLE,
 | |
|                         skipped_data,
 | |
|                         TRANSLOG_PAGE_SIZE - skipped_data))
 | |
|     {
 | |
|       DBUG_PRINT("error",
 | |
|                  ("Can't write page " LSN_FMT " to pagecache, error: %d",
 | |
|                   buffer->file->number,
 | |
|                   (uint)(LSN_OFFSET(buffer->offset)+ i),
 | |
|                   my_errno));
 | |
|       translog_stop_writing();
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     skipped_data= 0;
 | |
|   }
 | |
|   file->is_sync= 0;
 | |
|   if (my_pwrite(file->handler.file, buffer->buffer + buffer->skipped_data,
 | |
|                 buffer->size - buffer->skipped_data,
 | |
|                 LSN_OFFSET(buffer->offset) + buffer->skipped_data,
 | |
|                 log_write_flags))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Can't write buffer " LSN_FMT " size %lu "
 | |
|                          "to the disk (%d)",
 | |
|                          (uint) file->handler.file,
 | |
|                          (uint) LSN_OFFSET(buffer->offset),
 | |
|                          (ulong) buffer->size, errno));
 | |
|     translog_stop_writing();
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   /*
 | |
|     Dropping the flag in such way can make false alarm: signalling than the
 | |
|     file in not sync when it is sync, but the situation is quite rare and
 | |
|     protections with mutexes give much more overhead to the whole engine
 | |
|   */
 | |
|   file->is_sync= 0;
 | |
| 
 | |
|   if (LSN_OFFSET(buffer->last_lsn) != 0)    /* if buffer->last_lsn is set */
 | |
|   {
 | |
|     if (translog_prev_buffer_flush_wait(buffer))
 | |
|       DBUG_RETURN(0); /* some the thread flushed the buffer already */
 | |
|     translog_set_sent_to_disk(buffer);
 | |
|   }
 | |
|   else
 | |
|     translog_set_only_in_buffers(buffer->next_buffer_offset);
 | |
| 
 | |
|   /* say to next buffer that we are finished */
 | |
|   {
 | |
|     struct st_translog_buffer *next_buffer=
 | |
|       log_descriptor.buffers + ((buffer->buffer_no + 1) % TRANSLOG_BUFFERS_NO);
 | |
|     if (likely(translog_status == TRANSLOG_OK)){
 | |
|       translog_buffer_lock(next_buffer);
 | |
|       next_buffer->prev_sent_to_disk= buffer->offset;
 | |
|       translog_buffer_unlock(next_buffer);
 | |
|       mysql_cond_broadcast(&next_buffer->prev_sent_to_disk_cond);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         It is shutdown =>
 | |
|           1) there is only one thread
 | |
|           2) mutexes of other buffers can be destroyed => we can't use them
 | |
|       */
 | |
|       next_buffer->prev_sent_to_disk= buffer->offset;
 | |
|     }
 | |
|   }
 | |
|   /* Free buffer */
 | |
|   buffer->file= NULL;
 | |
|   buffer->overlay= 0;
 | |
|   buffer->ver++;
 | |
|   mysql_mutex_lock(&log_descriptor.dirty_buffer_mask_lock);
 | |
|   log_descriptor.dirty_buffer_mask&= ~(1 << buffer->buffer_no);
 | |
|   mysql_mutex_unlock(&log_descriptor.dirty_buffer_mask_lock);
 | |
|   mysql_cond_broadcast(&buffer->waiting_filling_buffer);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Recover page with sector protection (wipe out failed chunks)
 | |
| 
 | |
|   SYNOPSYS
 | |
|     translog_recover_page_up_to_sector()
 | |
|     page                 reference on the page
 | |
|     offset               offset of failed sector
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_recover_page_up_to_sector(uchar *page, uint16 offset)
 | |
| {
 | |
|   uint16 chunk_offset= translog_get_first_chunk_offset(page), valid_chunk_end;
 | |
|   DBUG_ENTER("translog_recover_page_up_to_sector");
 | |
|   DBUG_PRINT("enter", ("offset: %u  first chunk: %u",
 | |
|                        (uint) offset, (uint) chunk_offset));
 | |
| 
 | |
|   while (chunk_offset < offset && page[chunk_offset] != TRANSLOG_FILLER)
 | |
|   {
 | |
|     uint16 chunk_length;
 | |
|     if ((chunk_length=
 | |
|          translog_get_total_chunk_length(page, chunk_offset)) == 0)
 | |
|     {
 | |
|       DBUG_PRINT("error", ("cant get chunk length (offset %u)",
 | |
|                            (uint) chunk_offset));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     DBUG_PRINT("info", ("chunk: offset: %u  length %u",
 | |
|                         (uint) chunk_offset, (uint) chunk_length));
 | |
|     if (((ulong) chunk_offset) + ((ulong) chunk_length) > TRANSLOG_PAGE_SIZE)
 | |
|     {
 | |
|       DBUG_PRINT("error", ("damaged chunk (offset %u) in trusted area",
 | |
|                            (uint) chunk_offset));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     chunk_offset+= chunk_length;
 | |
|   }
 | |
| 
 | |
|   valid_chunk_end= chunk_offset;
 | |
|   /* end of trusted area - sector parsing */
 | |
|   while (page[chunk_offset] != TRANSLOG_FILLER)
 | |
|   {
 | |
|     uint16 chunk_length;
 | |
|     if ((chunk_length=
 | |
|          translog_get_total_chunk_length(page, chunk_offset)) == 0)
 | |
|       break;
 | |
| 
 | |
|     DBUG_PRINT("info", ("chunk: offset: %u  length %u",
 | |
|                         (uint) chunk_offset, (uint) chunk_length));
 | |
|     if (((ulong) chunk_offset) + ((ulong) chunk_length) >
 | |
|         (uint) (offset + DISK_DRIVE_SECTOR_SIZE))
 | |
|       break;
 | |
| 
 | |
|     chunk_offset+= chunk_length;
 | |
|     valid_chunk_end= chunk_offset;
 | |
|   }
 | |
|   DBUG_PRINT("info", ("valid chunk end offset: %u", (uint) valid_chunk_end));
 | |
| 
 | |
|   memset(page + valid_chunk_end, TRANSLOG_FILLER,
 | |
|          TRANSLOG_PAGE_SIZE - valid_chunk_end);
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Checks and removes sector protection.
 | |
| 
 | |
|   @param page            reference on the page content.
 | |
|   @param file            transaction log descriptor.
 | |
| 
 | |
|   @retvat 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_check_sector_protection(uchar *page, TRANSLOG_FILE *file)
 | |
| {
 | |
|   uint i, offset;
 | |
|   uchar *table= page + page_overhead[page[TRANSLOG_PAGE_FLAGS]] -
 | |
|     TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|   uint8 current= table[0];
 | |
|   DBUG_ENTER("translog_check_sector_protection");
 | |
| 
 | |
|   for (i= 1, offset= DISK_DRIVE_SECTOR_SIZE;
 | |
|        i < TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|        i++, offset+= DISK_DRIVE_SECTOR_SIZE)
 | |
|   {
 | |
|     /*
 | |
|       TODO: add chunk counting for "suspecting" sectors (difference is
 | |
|       more than 1-2), if difference more then present chunks then it is
 | |
|       the problem.
 | |
|     */
 | |
|     uint8 test= page[offset];
 | |
|     DBUG_PRINT("info", ("sector: #%u  offset: %u  current: %lx "
 | |
|                         "read: 0x%x  stored: 0x%x%x",
 | |
|                         i, offset, (ulong) current,
 | |
|                         (uint) uint2korr(page + offset), (uint) table[i],
 | |
|                         (uint) table[i + 1]));
 | |
|     /*
 | |
|       3 is minimal possible record length. So we can have "distance"
 | |
|       between 2 sectors value more then DISK_DRIVE_SECTOR_SIZE / 3
 | |
|       only if it is old value, i.e. the sector was not written.
 | |
|     */
 | |
|     if (((test < current) &&
 | |
|          ((uint)(0xFFL - current + test) > DISK_DRIVE_SECTOR_SIZE / 3)) ||
 | |
|         ((test >= current) &&
 | |
|          ((uint)(test - current) > DISK_DRIVE_SECTOR_SIZE / 3)))
 | |
|     {
 | |
|       if (translog_recover_page_up_to_sector(page, offset))
 | |
|         DBUG_RETURN(1);
 | |
|       file->was_recovered= 1;
 | |
|       DBUG_RETURN(0);
 | |
|     }
 | |
| 
 | |
|     /* Restore value on the page */
 | |
|     page[offset]= table[i];
 | |
|     current= test;
 | |
|     DBUG_PRINT("info", ("sector: #%u  offset: %u  current: %lx  "
 | |
|                         "read: 0x%x  stored: 0x%x",
 | |
|                         i, offset, (ulong) current,
 | |
|                         (uint) page[offset], (uint) table[i]));
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Log page validator (read callback)
 | |
| 
 | |
|   @param page            The page data to check
 | |
|   @param page_no         The page number (<offset>/<page length>)
 | |
|   @param data_ptr        Read callback data pointer (pointer to TRANSLOG_FILE)
 | |
| 
 | |
|   @todo: add turning loghandler to read-only mode after merging with
 | |
|   that patch.
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_page_validator(int res, PAGECACHE_IO_HOOK_ARGS *args)
 | |
| {
 | |
|   uchar *page= args->page;
 | |
|   pgcache_page_no_t page_no= args->pageno;
 | |
|   uint this_page_page_overhead;
 | |
|   uint flags;
 | |
|   uchar *page_pos;
 | |
|   TRANSLOG_FILE *data= (TRANSLOG_FILE *) args->data;
 | |
| #ifdef DBUG_TRACE
 | |
|   pgcache_page_no_t offset= page_no * TRANSLOG_PAGE_SIZE;
 | |
| #endif
 | |
|   DBUG_ENTER("translog_page_validator");
 | |
| 
 | |
|   data->was_recovered= 0;
 | |
| 
 | |
|   if (res)
 | |
|   {
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   if ((pgcache_page_no_t) uint3korr(page) != page_no ||
 | |
|       (uint32) uint3korr(page + 3) != data->number)
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Page " LSN_FMT ": "
 | |
|                          "page address written in the page is incorrect: "
 | |
|                          "File %lu instead of %lu or page %lu instead of %lu",
 | |
|                          (uint)data->number, (uint)offset,
 | |
|                          (ulong) uint3korr(page + 3), (ulong) data->number,
 | |
|                          (ulong) uint3korr(page),
 | |
|                          (ulong) page_no));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   flags= (uint)(page[TRANSLOG_PAGE_FLAGS]);
 | |
|   this_page_page_overhead= page_overhead[flags];
 | |
|   if (flags & ~(TRANSLOG_PAGE_CRC | TRANSLOG_SECTOR_PROTECTION |
 | |
|                 TRANSLOG_RECORD_CRC))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Page " LSN_FMT ": "
 | |
|                          "Garbage in the page flags field detected : %x",
 | |
|                          (uint) data->number, (uint) offset,
 | |
|                          (uint) flags));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   page_pos= page + (3 + 3 + 1);
 | |
|   if (flags & TRANSLOG_PAGE_CRC)
 | |
|   {
 | |
|     uint32 crc= translog_crc(page + this_page_page_overhead,
 | |
|                              TRANSLOG_PAGE_SIZE -
 | |
|                              this_page_page_overhead);
 | |
|     if (crc != uint4korr(page_pos))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("Page " LSN_FMT ": "
 | |
|                            "CRC mismatch: calculated: %lx on the page %lx",
 | |
|                            (uint) data->number, (uint) offset,
 | |
|                            (ulong) crc, (ulong) uint4korr(page_pos)));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     page_pos+= CRC_SIZE;                      /* Skip crc */
 | |
|   }
 | |
|   if (flags & TRANSLOG_SECTOR_PROTECTION &&
 | |
|       translog_check_sector_protection(page, data))
 | |
|   {
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Locks the loghandler.
 | |
| */
 | |
| 
 | |
| void translog_lock()
 | |
| {
 | |
|   uint8 current_buffer;
 | |
|   DBUG_ENTER("translog_lock");
 | |
| 
 | |
|   /*
 | |
|      Locking the loghandler mean locking current buffer, but it can change
 | |
|      during locking, so we should check it
 | |
|   */
 | |
|   for (;;)
 | |
|   {
 | |
|     /*
 | |
|       log_descriptor.bc.buffer_no is only one byte so its reading is
 | |
|       an atomic operation
 | |
|     */
 | |
|     current_buffer= log_descriptor.bc.buffer_no;
 | |
|     translog_buffer_lock(log_descriptor.buffers + current_buffer);
 | |
|     if (log_descriptor.bc.buffer_no == current_buffer)
 | |
|       break;
 | |
|     translog_buffer_unlock(log_descriptor.buffers + current_buffer);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Unlock the loghandler
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_unlock()
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| void translog_unlock()
 | |
| {
 | |
|   translog_buffer_unlock(log_descriptor.bc.buffer);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Get log page by file number and offset of the beginning of the page
 | |
| 
 | |
|   @param data            validator data, which contains the page address
 | |
|   @param buffer          buffer for page placing
 | |
|                          (might not be used in some cache implementations)
 | |
|   @param direct_link     if it is not NULL then caller can accept direct
 | |
|                          link to the page cache
 | |
| 
 | |
|   @retval NULL Error
 | |
|   @retval #    pointer to the page cache which should be used to read this page
 | |
| */
 | |
| 
 | |
| static uchar *translog_get_page(TRANSLOG_VALIDATOR_DATA *data, uchar *buffer,
 | |
|                                 PAGECACHE_BLOCK_LINK **direct_link)
 | |
| {
 | |
|   TRANSLOG_ADDRESS addr= *(data->addr), in_buffers;
 | |
|   uint32 file_no= LSN_FILE_NO(addr);
 | |
|   TRANSLOG_FILE *file;
 | |
|   DBUG_ENTER("translog_get_page");
 | |
|   DBUG_PRINT("enter", ("File: %u  Offset: %u(0x%x)",
 | |
|                        file_no,
 | |
|                        (uint) LSN_OFFSET(addr),
 | |
|                        (uint) LSN_OFFSET(addr)));
 | |
| 
 | |
|   /* it is really page address */
 | |
|   DBUG_ASSERT(LSN_OFFSET(addr) % TRANSLOG_PAGE_SIZE == 0);
 | |
|   if (direct_link)
 | |
|     *direct_link= NULL;
 | |
| 
 | |
| restart:
 | |
| 
 | |
|   in_buffers= translog_only_in_buffers();
 | |
|   DBUG_PRINT("info", ("in_buffers: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(in_buffers)));
 | |
|   if (in_buffers != LSN_IMPOSSIBLE &&
 | |
|       cmp_translog_addr(addr, in_buffers) >= 0)
 | |
|   {
 | |
|     translog_lock();
 | |
|     DBUG_ASSERT(cmp_translog_addr(addr, log_descriptor.horizon) < 0);
 | |
|     /* recheck with locked loghandler */
 | |
|     in_buffers= translog_only_in_buffers();
 | |
|     if (cmp_translog_addr(addr, in_buffers) >= 0)
 | |
|     {
 | |
|       uint16 buffer_no= log_descriptor.bc.buffer_no;
 | |
| #ifdef DBUG_ASSERT_EXISTS
 | |
|       uint16 buffer_start= buffer_no;
 | |
| #endif
 | |
|       struct st_translog_buffer *buffer_unlock= log_descriptor.bc.buffer;
 | |
|       struct st_translog_buffer *curr_buffer= log_descriptor.bc.buffer;
 | |
|       for (;;)
 | |
|       {
 | |
|         /*
 | |
|           if the page is in the buffer and it is the last version of the
 | |
|           page (in case of division the page by buffer flush)
 | |
|         */
 | |
|         if (curr_buffer->file != NULL &&
 | |
|             cmp_translog_addr(addr, curr_buffer->offset) >= 0 &&
 | |
|             cmp_translog_addr(addr,
 | |
|                               (curr_buffer->next_buffer_offset ?
 | |
|                                curr_buffer->next_buffer_offset:
 | |
|                                curr_buffer->offset + curr_buffer->size)) < 0)
 | |
|         {
 | |
|           TRANSLOG_ADDRESS offset= curr_buffer->offset;
 | |
|           TRANSLOG_FILE *fl= curr_buffer->file;
 | |
|           uchar *from, *table= NULL;
 | |
|           int is_last_unfinished_page;
 | |
|           uint last_protected_sector= 0;
 | |
|           uint skipped_data= curr_buffer->skipped_data;
 | |
|           TRANSLOG_FILE file_copy;
 | |
|           uint8 ver= curr_buffer->ver;
 | |
|           translog_wait_for_writers(curr_buffer);
 | |
|           if (offset != curr_buffer->offset || fl != curr_buffer->file ||
 | |
|               ver != curr_buffer->ver)
 | |
|           {
 | |
|             DBUG_ASSERT(buffer_unlock == curr_buffer);
 | |
|             translog_buffer_unlock(buffer_unlock);
 | |
|             goto restart;
 | |
|           }
 | |
|           DBUG_ASSERT(LSN_FILE_NO(addr) ==  LSN_FILE_NO(curr_buffer->offset));
 | |
|           from= curr_buffer->buffer + (addr - curr_buffer->offset);
 | |
|           if (skipped_data && addr == curr_buffer->offset)
 | |
|           {
 | |
|             /*
 | |
|               We read page part of which is not present in buffer,
 | |
|               so we should read absent part from file (page cache actually)
 | |
|             */
 | |
|             file= get_logfile_by_number(file_no);
 | |
|             DBUG_ASSERT(file != NULL);
 | |
|             /*
 | |
|               it's ok to not lock the page because:
 | |
|                 - The log handler has it's own page cache.
 | |
|                 - There is only one thread that can access the log
 | |
|                 cache at a time
 | |
|             */
 | |
|             if (!(buffer= pagecache_read(log_descriptor.pagecache,
 | |
|                                          &file->handler,
 | |
|                                          LSN_OFFSET(addr) / TRANSLOG_PAGE_SIZE,
 | |
|                                          3, buffer,
 | |
|                                          PAGECACHE_PLAIN_PAGE,
 | |
|                                          PAGECACHE_LOCK_LEFT_UNLOCKED,
 | |
|                                          NULL)))
 | |
|               DBUG_RETURN(NULL);
 | |
|           }
 | |
|           else
 | |
|             skipped_data= 0;  /* Read after skipped in buffer data */
 | |
|           /*
 | |
|             Now we have correct data in buffer up to 'skipped_data'. The
 | |
|             following memcpy() will move the data from the internal buffer
 | |
|             that was not yet on disk.
 | |
|           */
 | |
|           memcpy(buffer + skipped_data, from + skipped_data,
 | |
|                  TRANSLOG_PAGE_SIZE - skipped_data);
 | |
|           /*
 | |
|             We can use copy then in translog_page_validator() because it
 | |
|             do not put it permanently somewhere.
 | |
|             We have to use copy because after releasing log lock we can't
 | |
|             guaranty that the file still be present (in real life it will be
 | |
|             present but theoretically possible that it will be released
 | |
|             already from last files cache);
 | |
|           */
 | |
|           file_copy= *(curr_buffer->file);
 | |
|           file_copy.handler.callback_data= (uchar*) &file_copy;
 | |
|           is_last_unfinished_page= ((log_descriptor.bc.buffer ==
 | |
|                                      curr_buffer) &&
 | |
|                                     (log_descriptor.bc.ptr >= from) &&
 | |
|                                     (log_descriptor.bc.ptr <
 | |
|                                      from + TRANSLOG_PAGE_SIZE));
 | |
|           if (is_last_unfinished_page &&
 | |
|               (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION))
 | |
|           {
 | |
|             last_protected_sector= ((log_descriptor.bc.previous_offset - 1) /
 | |
|                                     DISK_DRIVE_SECTOR_SIZE);
 | |
|             table= buffer + log_descriptor.page_overhead -
 | |
|               TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|           }
 | |
| 
 | |
|           DBUG_ASSERT(buffer_unlock == curr_buffer);
 | |
|           translog_buffer_unlock(buffer_unlock);
 | |
|           if (is_last_unfinished_page)
 | |
|           {
 | |
|             uint i;
 | |
|             /*
 | |
|               This is last unfinished page => we should not check CRC and
 | |
|               remove only that protection which already installed (no need
 | |
|               to check it)
 | |
| 
 | |
|               We do not check the flag of sector protection, because if
 | |
|               (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION) is
 | |
|               not set then last_protected_sector will be 0 so following loop
 | |
|               will be never executed
 | |
|             */
 | |
|             DBUG_PRINT("info", ("This is last unfinished page, "
 | |
|                                 "last protected sector %u",
 | |
|                                 last_protected_sector));
 | |
|             for (i= 1; i <= last_protected_sector; i++)
 | |
|             {
 | |
|               uint offset= i * DISK_DRIVE_SECTOR_SIZE;
 | |
|               DBUG_PRINT("info", ("Sector %u: 0x%02x <- 0x%02x",
 | |
|                                   i, buffer[offset],
 | |
|                                   table[i]));
 | |
|               buffer[offset]= table[i];
 | |
|             }
 | |
|           }
 | |
|           else
 | |
|           {
 | |
|             /*
 | |
|               This IF should be true because we use in-memory data which
 | |
|               supposed to be correct.
 | |
|             */
 | |
|             PAGECACHE_IO_HOOK_ARGS args;
 | |
|             args.page= buffer;
 | |
|             args.pageno= LSN_OFFSET(addr) / TRANSLOG_PAGE_SIZE;
 | |
|             args.data= (uchar*) &file_copy;
 | |
|             if (translog_page_validator(0, &args))
 | |
|             {
 | |
|               DBUG_ASSERT(0);
 | |
|               buffer= NULL;
 | |
|             }
 | |
|           }
 | |
|           DBUG_RETURN(buffer);
 | |
|         }
 | |
|         buffer_no= (buffer_no + 1) % TRANSLOG_BUFFERS_NO;
 | |
|         curr_buffer= log_descriptor.buffers + buffer_no;
 | |
|         translog_buffer_lock(curr_buffer);
 | |
|         translog_buffer_unlock(buffer_unlock);
 | |
|         buffer_unlock= curr_buffer;
 | |
|         /* we can't make a full circle */
 | |
|         DBUG_ASSERT(buffer_start != buffer_no);
 | |
|       }
 | |
|     }
 | |
|     translog_unlock();
 | |
|   }
 | |
|   file= get_logfile_by_number(file_no);
 | |
|   DBUG_ASSERT(file != NULL);
 | |
|   buffer= pagecache_read(log_descriptor.pagecache, &file->handler,
 | |
|                          LSN_OFFSET(addr) / TRANSLOG_PAGE_SIZE,
 | |
|                          3, (direct_link ? NULL : buffer),
 | |
|                          PAGECACHE_PLAIN_PAGE,
 | |
|                          (direct_link ?
 | |
|                           PAGECACHE_LOCK_READ :
 | |
|                           PAGECACHE_LOCK_LEFT_UNLOCKED),
 | |
|                          direct_link);
 | |
|   DBUG_PRINT("info", ("Direct link is assigned to : %p * %p",
 | |
|                       direct_link,
 | |
|                       (direct_link ? *direct_link : NULL)));
 | |
|   data->was_recovered= file->was_recovered;
 | |
|   DBUG_RETURN(buffer);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief free direct log page link
 | |
| 
 | |
|   @param direct_link the direct log page link to be freed
 | |
| 
 | |
| */
 | |
| 
 | |
| static void translog_free_link(PAGECACHE_BLOCK_LINK *direct_link)
 | |
| {
 | |
|   DBUG_ENTER("translog_free_link");
 | |
|   DBUG_PRINT("info", ("Direct link: %p",
 | |
|                       direct_link));
 | |
|   if (direct_link)
 | |
|     pagecache_unlock_by_link(log_descriptor.pagecache, direct_link,
 | |
|                              PAGECACHE_LOCK_READ_UNLOCK, PAGECACHE_UNPIN,
 | |
|                              LSN_IMPOSSIBLE, LSN_IMPOSSIBLE, 0, FALSE);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Finds last full page of the given log file.
 | |
| 
 | |
|   @param addr            address structure to fill with data, which contain
 | |
|                          file number of the log file
 | |
|   @param last_page_ok    Result of the check whether last page OK.
 | |
|                          (for now only we check only that file length
 | |
|                          divisible on page length).
 | |
|   @param no_errors       suppress messages about non-critical errors
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_get_last_page_addr(TRANSLOG_ADDRESS *addr,
 | |
|                                            my_bool *last_page_ok,
 | |
|                                            my_bool no_errors)
 | |
| {
 | |
|   char path[FN_REFLEN];
 | |
|   uint32 rec_offset;
 | |
|   my_off_t file_size;
 | |
|   uint32 file_no= LSN_FILE_NO(*addr);
 | |
|   TRANSLOG_FILE *file;
 | |
| #ifdef DBUG_TRACE
 | |
|   char buff[21];
 | |
| #endif
 | |
|   DBUG_ENTER("translog_get_last_page_addr");
 | |
| 
 | |
|   if (likely((file= get_logfile_by_number(file_no)) != NULL))
 | |
|   {
 | |
|     /*
 | |
|       This function used only during initialization of loghandler or in
 | |
|       scanner (which mean we need read that part of the log), so the
 | |
|       requested log file have to be opened and can't be freed after
 | |
|       returning pointer on it (file_size).
 | |
|     */
 | |
|     file_size= mysql_file_seek(file->handler.file, 0, SEEK_END, MYF(0));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       This branch is used only during very early initialization
 | |
|       when files are not opened.
 | |
|     */
 | |
|     File fd;
 | |
|     if ((fd= mysql_file_open(key_file_translog,
 | |
|                              translog_filename_by_fileno(file_no, path),
 | |
|                              O_RDONLY | O_CLOEXEC, (no_errors ? MYF(0) : MYF(MY_WME)))) < 0)
 | |
|     {
 | |
|       my_errno= errno;
 | |
|       DBUG_PRINT("error", ("Error %d during opening file #%d",
 | |
|                            errno, file_no));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     file_size= mysql_file_seek(fd, 0, SEEK_END, MYF(0));
 | |
|     mysql_file_close(fd, MYF(0));
 | |
|   }
 | |
|   DBUG_PRINT("info", ("File size: %s", llstr(file_size, buff)));
 | |
|   if (file_size == MY_FILEPOS_ERROR)
 | |
|     DBUG_RETURN(1);
 | |
|   DBUG_ASSERT(file_size < 0xffffffffULL);
 | |
|   if (((uint32)file_size) > TRANSLOG_PAGE_SIZE)
 | |
|   {
 | |
|     rec_offset= (((((uint32)file_size) / TRANSLOG_PAGE_SIZE) - 1) *
 | |
|                        TRANSLOG_PAGE_SIZE);
 | |
|     *last_page_ok= (((uint32)file_size) == rec_offset + TRANSLOG_PAGE_SIZE);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     *last_page_ok= 0;
 | |
|     rec_offset= 0;
 | |
|   }
 | |
|   *addr= MAKE_LSN(file_no, rec_offset);
 | |
|   DBUG_PRINT("info", ("Last page: 0x%lx  ok: %d", (ulong) rec_offset,
 | |
|                       *last_page_ok));
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Get number bytes for record length storing
 | |
| 
 | |
|   @param length          Record length which will be encoded
 | |
| 
 | |
|   @return 1,3,4,5 - number of bytes to store given length
 | |
| */
 | |
| 
 | |
| static uint translog_variable_record_length_bytes(translog_size_t length)
 | |
| {
 | |
|   if (length < 250)
 | |
|     return 1;
 | |
|   if (length < 0xFFFF)
 | |
|     return 3;
 | |
|   if (length < (ulong) 0xFFFFFF)
 | |
|     return 4;
 | |
|   return 5;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Gets header of this chunk.
 | |
| 
 | |
|   @param chunk           The pointer to the chunk beginning
 | |
| 
 | |
|   @retval # total length of the chunk
 | |
|   @retval 0 Error
 | |
| */
 | |
| 
 | |
| static uint16 translog_get_chunk_header_length(uchar *chunk)
 | |
| {
 | |
|   DBUG_ENTER("translog_get_chunk_header_length");
 | |
|   switch (*chunk & TRANSLOG_CHUNK_TYPE) {
 | |
|   case TRANSLOG_CHUNK_LSN:
 | |
|   {
 | |
|     /* 0 chunk referred as LSN (head or tail) */
 | |
|     translog_size_t rec_len __attribute__((unused));
 | |
|     uchar *start= chunk;
 | |
|     uchar *ptr= start + 1 + 2;
 | |
|     uint16 chunk_len, header_len;
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_LSN"));
 | |
|     rec_len= translog_variable_record_1group_decode_len(&ptr);
 | |
|     chunk_len= uint2korr(ptr);
 | |
|     header_len= (uint16) (ptr - start) +2;
 | |
|     DBUG_PRINT("info", ("rec len: %lu  chunk len: %u  header len: %u",
 | |
|                         (ulong) rec_len, (uint) chunk_len, (uint) header_len));
 | |
|     if (chunk_len)
 | |
|     {
 | |
|       /* TODO: fine header end */
 | |
|       /*
 | |
|         The last chunk of multi-group record can be base for it header
 | |
|         calculation (we skip to the first group to read the header) so if we
 | |
|         stuck here something is wrong.
 | |
|       */
 | |
|       DBUG_ASSERT(0);
 | |
|       DBUG_RETURN(0);                               /* Keep compiler happy */
 | |
|     }
 | |
|     DBUG_RETURN(header_len);
 | |
|   }
 | |
|   case TRANSLOG_CHUNK_FIXED:
 | |
|   {
 | |
|     /* 1 (pseudo)fixed record (also LSN) */
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_FIXED = 3"));
 | |
|     DBUG_RETURN(3);
 | |
|   }
 | |
|   case TRANSLOG_CHUNK_NOHDR:
 | |
|     /* 2 no header chunk (till page end) */
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_NOHDR = 1"));
 | |
|     DBUG_RETURN(1);
 | |
|     break;
 | |
|   case TRANSLOG_CHUNK_LNGTH:
 | |
|     /* 3 chunk with chunk length */
 | |
|     DBUG_PRINT("info", ("TRANSLOG_CHUNK_LNGTH = 3"));
 | |
|     DBUG_RETURN(3);
 | |
|     break;
 | |
|   }
 | |
|   DBUG_ASSERT(0);
 | |
|   DBUG_RETURN(0);                               /* Keep compiler happy */
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Truncate the log to the given address. Used during the startup if the
 | |
|          end of log if corrupted.
 | |
| 
 | |
|   @param addr            new horizon
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_truncate_log(TRANSLOG_ADDRESS addr)
 | |
| {
 | |
|   uchar *page;
 | |
|   TRANSLOG_ADDRESS current_page;
 | |
|   uint32 next_page_offset, page_rest;
 | |
|   uint32 i;
 | |
|   File fd;
 | |
|   int rc;
 | |
|   TRANSLOG_VALIDATOR_DATA data;
 | |
|   char path[FN_REFLEN];
 | |
|   uchar page_buff[TRANSLOG_PAGE_SIZE];
 | |
|   DBUG_ENTER("translog_truncate_log");
 | |
|   /* TODO: write warning to the client */
 | |
|   DBUG_PRINT("warning", ("removing all records from " LSN_FMT " "
 | |
|                          "till " LSN_FMT,
 | |
|                          LSN_IN_PARTS(addr),
 | |
|                          LSN_IN_PARTS(log_descriptor.horizon)));
 | |
|   DBUG_ASSERT(cmp_translog_addr(addr, log_descriptor.horizon) < 0);
 | |
|   /* remove files between the address and horizon */
 | |
|   for (i= LSN_FILE_NO(addr) + 1; i <= LSN_FILE_NO(log_descriptor.horizon); i++)
 | |
|     if (mysql_file_delete(key_file_translog,
 | |
|                           translog_filename_by_fileno(i, path),  MYF(MY_WME)))
 | |
|     {
 | |
|       translog_unlock();
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
| 
 | |
|   /* truncate the last file up to the last page */
 | |
|   next_page_offset= LSN_OFFSET(addr);
 | |
|   next_page_offset= (next_page_offset -
 | |
|                      ((next_page_offset - 1) % TRANSLOG_PAGE_SIZE + 1) +
 | |
|                      TRANSLOG_PAGE_SIZE);
 | |
|   page_rest= next_page_offset - LSN_OFFSET(addr);
 | |
|   memset(page_buff, TRANSLOG_FILLER, page_rest);
 | |
|   rc= ((fd= open_logfile_by_number_no_cache(LSN_FILE_NO(addr))) < 0 ||
 | |
|        ((mysql_file_chsize(fd, next_page_offset, TRANSLOG_FILLER, MYF(MY_WME)) ||
 | |
|          (page_rest && my_pwrite(fd, page_buff, page_rest, LSN_OFFSET(addr),
 | |
|                                  log_write_flags)) ||
 | |
|          mysql_file_sync(fd, MYF(MY_WME)))));
 | |
|   translog_syncs++;
 | |
|   rc|= (fd > 0 && mysql_file_close(fd, MYF(MY_WME)));
 | |
|   if (sync_log_dir >= TRANSLOG_SYNC_DIR_ALWAYS)
 | |
|   {
 | |
|     rc|= sync_dir(log_descriptor.directory_fd, MYF(MY_WME | MY_IGNORE_BADFD));
 | |
|     translog_syncs++;
 | |
|   }
 | |
|   if (rc)
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   /* fix the horizon */
 | |
|   log_descriptor.horizon= addr;
 | |
|   /* fix the buffer data */
 | |
|   current_page= MAKE_LSN(LSN_FILE_NO(addr), (next_page_offset -
 | |
|                                              TRANSLOG_PAGE_SIZE));
 | |
|   data.addr= ¤t_page;
 | |
|   if ((page= translog_get_page(&data, log_descriptor.buffers->buffer, NULL)) ==
 | |
|       NULL)
 | |
|     DBUG_RETURN(1);
 | |
|   if (page != log_descriptor.buffers->buffer)
 | |
|     memcpy(log_descriptor.buffers->buffer, page, TRANSLOG_PAGE_SIZE);
 | |
|   log_descriptor.bc.buffer->offset= current_page;
 | |
|   log_descriptor.bc.buffer->size= LSN_OFFSET(addr) - LSN_OFFSET(current_page);
 | |
|   log_descriptor.bc.ptr=
 | |
|     log_descriptor.buffers->buffer + log_descriptor.bc.buffer->size;
 | |
|   log_descriptor.bc.current_page_fill= log_descriptor.bc.buffer->size;
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Applies function 'callback' to all files (in a directory) which
 | |
|   name looks like a log's name (aria_log.[0-9]{7}).
 | |
|   If 'callback' returns TRUE this interrupts the walk and returns
 | |
|   TRUE. Otherwise FALSE is returned after processing all log files.
 | |
|   It cannot just use log_descriptor.directory because that may not yet have
 | |
|   been initialized.
 | |
| 
 | |
|   @param  directory        directory to scan
 | |
|   @param  callback         function to apply; is passed directory and base
 | |
|                            name of found file
 | |
| */
 | |
| 
 | |
| my_bool translog_walk_filenames(const char *directory,
 | |
|                                 my_bool (*callback)(const char *,
 | |
|                                                     const char *))
 | |
| {
 | |
|   MY_DIR *dirp;
 | |
|   uint i;
 | |
|   my_bool rc= FALSE;
 | |
| 
 | |
|   /* Finds and removes transaction log files */
 | |
|   if (!(dirp = my_dir(directory, MYF(MY_DONT_SORT))))
 | |
|     return FALSE;
 | |
| 
 | |
|   for (i= 0; i < dirp->number_of_files; i++)
 | |
|   {
 | |
|     char *file= dirp->dir_entry[i].name;
 | |
|     if (strncmp(file, "aria_log.", 10) == 0 &&
 | |
|         file[10] >= '0' && file[10] <= '9' &&
 | |
|         file[11] >= '0' && file[11] <= '9' &&
 | |
|         file[12] >= '0' && file[12] <= '9' &&
 | |
|         file[13] >= '0' && file[13] <= '9' &&
 | |
|         file[14] >= '0' && file[14] <= '9' &&
 | |
|         file[15] >= '0' && file[15] <= '9' &&
 | |
|         file[16] >= '0' && file[16] <= '9' &&
 | |
|         file[17] >= '0' && file[17] <= '9' &&
 | |
|         file[18] == '\0' && (*callback)(directory, file))
 | |
|     {
 | |
|       rc= TRUE;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   my_dirend(dirp);
 | |
|   return rc;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Fills table of dependence length of page header from page flags
 | |
| */
 | |
| 
 | |
| void translog_fill_overhead_table()
 | |
| {
 | |
|   uint i;
 | |
|   for (i= 0; i < TRANSLOG_FLAGS_NUM; i++)
 | |
|   {
 | |
|      page_overhead[i]= 7;
 | |
|      if (i & TRANSLOG_PAGE_CRC)
 | |
|        page_overhead[i]+= CRC_SIZE;
 | |
|      if (i & TRANSLOG_SECTOR_PROTECTION)
 | |
|        page_overhead[i]+= TRANSLOG_PAGE_SIZE /
 | |
|                            DISK_DRIVE_SECTOR_SIZE;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Callback to find first log in directory.
 | |
| */
 | |
| 
 | |
| static my_bool translog_callback_search_first(const char *directory
 | |
|                                               __attribute__((unused)),
 | |
|                                               const char *filename
 | |
|                                               __attribute__((unused)))
 | |
| {
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Checks that chunk is LSN one
 | |
| 
 | |
|   @param type            type of the chunk
 | |
| 
 | |
|   @retval 1 the chunk is LNS
 | |
|   @retval 0 the chunk is not LSN
 | |
| */
 | |
| 
 | |
| static my_bool translog_is_LSN_chunk(uchar type)
 | |
| {
 | |
|   DBUG_ENTER("translog_is_LSN_chunk");
 | |
|   DBUG_PRINT("info", ("byte: %x  chunk type: %u  record type: %u",
 | |
|                       type, type >> 6, type & TRANSLOG_REC_TYPE));
 | |
|   DBUG_RETURN(((type & TRANSLOG_CHUNK_TYPE) == TRANSLOG_CHUNK_FIXED) ||
 | |
|               (((type & TRANSLOG_CHUNK_TYPE) == TRANSLOG_CHUNK_LSN)  &&
 | |
|                ((type & TRANSLOG_REC_TYPE)) != TRANSLOG_CHUNK_0_CONT));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Initialize transaction log
 | |
| 
 | |
|   @param directory       Directory where log files are put
 | |
|   @param log_file_max_size max size of one log size (for new logs creation)
 | |
|   @param server_version  version of MySQL server (MYSQL_VERSION_ID)
 | |
|   @param server_id       server ID (replication & Co)
 | |
|   @param pagecache       Page cache for the log reads
 | |
|   @param flags           flags (TRANSLOG_PAGE_CRC, TRANSLOG_SECTOR_PROTECTION
 | |
|                            TRANSLOG_RECORD_CRC)
 | |
|   @param read_only       Put transaction log in read-only mode
 | |
|   @param init_table_func function to initialize record descriptors table
 | |
|   @param no_errors       suppress messages about non-critical errors
 | |
| 
 | |
|   @todo
 | |
|     Free used resources in case of error.
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| /* Stack size 26120 from clang */
 | |
| PRAGMA_DISABLE_CHECK_STACK_FRAME
 | |
| 
 | |
| my_bool translog_init_with_table(const char *directory,
 | |
|                                  uint32 log_file_max_size,
 | |
|                                  uint32 server_version,
 | |
|                                  uint32 server_id, PAGECACHE *pagecache,
 | |
|                                  uint flags, my_bool readonly,
 | |
|                                  void (*init_table_func)(),
 | |
|                                  my_bool no_errors)
 | |
| {
 | |
|   int i;
 | |
|   int old_log_was_recovered= 0, logs_found= 0;
 | |
|   uint old_flags= flags;
 | |
|   uint32 start_file_num= 1;
 | |
|   TRANSLOG_ADDRESS UNINIT_VAR(sure_page), last_page, last_valid_page,
 | |
|     checkpoint_lsn;
 | |
|   my_bool version_changed= 0;
 | |
|   DBUG_ENTER("translog_init_with_table");
 | |
| 
 | |
|   translog_syncs= 0;
 | |
|   flush_start= 0;
 | |
|   id_to_share= NULL;
 | |
|   log_purge_disabled= 0;
 | |
| 
 | |
|   log_descriptor.directory_fd= -1;
 | |
|   log_descriptor.is_everything_flushed= 1;
 | |
|   log_descriptor.flush_in_progress= 0;
 | |
|   log_descriptor.flush_no= 0;
 | |
|   log_descriptor.next_pass_max_lsn= LSN_IMPOSSIBLE;
 | |
| 
 | |
|   /* Normally in Aria this this calls translog_table_init() */
 | |
|   (*init_table_func)();
 | |
|   compile_time_assert(sizeof(log_descriptor.dirty_buffer_mask) * 8 >=
 | |
|                       TRANSLOG_BUFFERS_NO);
 | |
|   log_descriptor.dirty_buffer_mask= 0;
 | |
|   if (readonly)
 | |
|     log_descriptor.open_flags= O_BINARY | O_RDONLY;
 | |
|   else
 | |
|     log_descriptor.open_flags= O_BINARY | O_RDWR;
 | |
|   if (mysql_mutex_init(key_TRANSLOG_BUFFER_mutex,
 | |
|                        &log_descriptor.sent_to_disk_lock, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_mutex_init(key_TRANSLOG_DESCRIPTOR_file_header_lock,
 | |
|                        &log_descriptor.file_header_lock, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_mutex_init(key_TRANSLOG_DESCRIPTOR_unfinished_files_lock,
 | |
|                        &log_descriptor.unfinished_files_lock, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_mutex_init(key_TRANSLOG_DESCRIPTOR_purger_lock,
 | |
|                        &log_descriptor.purger_lock, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_mutex_init(key_TRANSLOG_DESCRIPTOR_log_flush_lock,
 | |
|                        &log_descriptor.log_flush_lock, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_mutex_init(key_TRANSLOG_DESCRIPTOR_dirty_buffer_mask_lock,
 | |
|                        &log_descriptor.dirty_buffer_mask_lock, MY_MUTEX_INIT_FAST) ||
 | |
|       mysql_cond_init(key_TRANSLOG_DESCRIPTOR_log_flush_cond,
 | |
|                       &log_descriptor.log_flush_cond, 0) ||
 | |
|       mysql_cond_init(key_TRANSLOG_DESCRIPTOR_new_goal_cond,
 | |
|                       &log_descriptor.new_goal_cond, 0) ||
 | |
|       mysql_rwlock_init(key_TRANSLOG_DESCRIPTOR_open_files_lock,
 | |
|                         &log_descriptor.open_files_lock) ||
 | |
|       my_init_dynamic_array(PSI_INSTRUMENT_ME, &log_descriptor.open_files,
 | |
|                             sizeof(TRANSLOG_FILE*), 10, 10, MYF(0)) ||
 | |
|       my_init_dynamic_array(PSI_INSTRUMENT_ME, &log_descriptor.unfinished_files,
 | |
|                             sizeof(struct st_file_counter),
 | |
|                             10, 10, MYF(0)))
 | |
|     goto err;
 | |
|   log_descriptor.min_need_file= 0;
 | |
|   log_descriptor.min_file_number= 0;
 | |
|   log_descriptor.last_lsn_checked= LSN_IMPOSSIBLE;
 | |
| 
 | |
|   /* Directory to store files */
 | |
|   unpack_dirname(log_descriptor.directory, directory);
 | |
| #ifndef _WIN32
 | |
|   if ((log_descriptor.directory_fd= my_open(log_descriptor.directory,
 | |
|                                             O_RDONLY, MYF(MY_WME))) < 0)
 | |
|   {
 | |
|     my_errno= errno;
 | |
|     DBUG_PRINT("error", ("Error %d during opening directory '%s'",
 | |
|                          errno, log_descriptor.directory));
 | |
|     goto err;
 | |
|   }
 | |
| #endif
 | |
|   log_descriptor.in_buffers_only= LSN_IMPOSSIBLE;
 | |
|   DBUG_ASSERT(log_file_max_size % TRANSLOG_PAGE_SIZE == 0 &&
 | |
|               log_file_max_size >= TRANSLOG_MIN_FILE_SIZE);
 | |
|   /* max size of one log size (for new logs creation) */
 | |
|   log_file_size= log_descriptor.log_file_max_size=
 | |
|     log_file_max_size;
 | |
|   /* server version */
 | |
|   log_descriptor.server_version= server_version;
 | |
|   /* server ID */
 | |
|   log_descriptor.server_id= server_id;
 | |
|   /* Page cache for the log reads */
 | |
|   log_descriptor.pagecache= pagecache;
 | |
|   /* Flags */
 | |
|   DBUG_ASSERT((flags &
 | |
|                ~(TRANSLOG_PAGE_CRC | TRANSLOG_SECTOR_PROTECTION |
 | |
|                  TRANSLOG_RECORD_CRC)) == 0);
 | |
|   log_descriptor.flags= flags;
 | |
|   translog_fill_overhead_table();
 | |
|   log_descriptor.page_overhead= page_overhead[flags];
 | |
|   log_descriptor.page_capacity_chunk_2=
 | |
|     TRANSLOG_PAGE_SIZE - log_descriptor.page_overhead - 1;
 | |
|   compile_time_assert(TRANSLOG_WRITE_BUFFER % TRANSLOG_PAGE_SIZE == 0);
 | |
|   log_descriptor.buffer_capacity_chunk_2=
 | |
|     (TRANSLOG_WRITE_BUFFER / TRANSLOG_PAGE_SIZE) *
 | |
|     log_descriptor.page_capacity_chunk_2;
 | |
|   log_descriptor.half_buffer_capacity_chunk_2=
 | |
|     log_descriptor.buffer_capacity_chunk_2 / 2;
 | |
|   DBUG_PRINT("info",
 | |
|              ("Overhead: %u  pc2: %u  bc2: %u,  bc2/2: %u",
 | |
|               log_descriptor.page_overhead,
 | |
|               log_descriptor.page_capacity_chunk_2,
 | |
|               log_descriptor.buffer_capacity_chunk_2,
 | |
|               log_descriptor.half_buffer_capacity_chunk_2));
 | |
| 
 | |
|   /* Just to init it somehow (hack for bootstrap)*/
 | |
|   {
 | |
|     TRANSLOG_FILE *file= 0;
 | |
|     log_descriptor.min_file = log_descriptor.max_file= 1;
 | |
|     insert_dynamic(&log_descriptor.open_files, (uchar *)&file);
 | |
|     translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0);
 | |
|     pop_dynamic(&log_descriptor.open_files);
 | |
|   }
 | |
| 
 | |
|   /* Buffers for log writing */
 | |
|   for (i= 0; i < TRANSLOG_BUFFERS_NO; i++)
 | |
|   {
 | |
|     if (translog_buffer_init(log_descriptor.buffers + i, i))
 | |
|       goto err;
 | |
|     DBUG_PRINT("info", ("translog_buffer buffer #%u:%p",
 | |
|                         i, log_descriptor.buffers + i));
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     last_logno and last_checkpoint_lsn were set in
 | |
|     ma_control_file_create_or_open()
 | |
|   */
 | |
|   logs_found= (last_logno != FILENO_IMPOSSIBLE);
 | |
| 
 | |
|   translog_status= (readonly ? TRANSLOG_READONLY : TRANSLOG_OK);
 | |
|   checkpoint_lsn= last_checkpoint_lsn;
 | |
| 
 | |
|   if (logs_found)
 | |
|   {
 | |
|     my_bool pageok;
 | |
|     DBUG_PRINT("info", ("log found..."));
 | |
|     /*
 | |
|       TODO: scan directory for aria_log.XXXXXXXX files and find
 | |
|        highest XXXXXXXX & set logs_found
 | |
|       TODO: check that last checkpoint within present log addresses space
 | |
| 
 | |
|       find the log end
 | |
|     */
 | |
|     if (LSN_FILE_NO(last_checkpoint_lsn) == FILENO_IMPOSSIBLE)
 | |
|     {
 | |
|       DBUG_ASSERT(LSN_OFFSET(last_checkpoint_lsn) == 0);
 | |
|       /* only last log needs to be checked */
 | |
|       sure_page= MAKE_LSN(last_logno, TRANSLOG_PAGE_SIZE);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       sure_page= last_checkpoint_lsn;
 | |
|       DBUG_ASSERT(LSN_OFFSET(sure_page) % TRANSLOG_PAGE_SIZE != 0);
 | |
|       sure_page-= LSN_OFFSET(sure_page) % TRANSLOG_PAGE_SIZE;
 | |
|     }
 | |
|     /* Set horizon to the beginning of the last file first */
 | |
|     log_descriptor.horizon= last_page= MAKE_LSN(last_logno, 0);
 | |
|     if (translog_get_last_page_addr(&last_page, &pageok, no_errors))
 | |
|     {
 | |
|       if (!translog_walk_filenames(log_descriptor.directory,
 | |
|                                    &translog_callback_search_first))
 | |
|       {
 | |
|         /*
 | |
|           Files was deleted, just start from the next log number, so that
 | |
|           existing tables are in the past.
 | |
|         */
 | |
|         start_file_num= last_logno + 1;
 | |
|         checkpoint_lsn= LSN_IMPOSSIBLE; /* no log so no checkpoint */
 | |
|         logs_found= 0;
 | |
|       }
 | |
|       else
 | |
|         goto err;
 | |
|     }
 | |
|     else if (LSN_OFFSET(last_page) == 0)
 | |
|     {
 | |
|       if (LSN_FILE_NO(last_page) == 1)
 | |
|       {
 | |
|         logs_found= 0;                          /* file #1 has no pages */
 | |
|         DBUG_PRINT("info", ("log found. But is is empty => no log assumed"));
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         last_page-= LSN_ONE_FILE;
 | |
|         if (translog_get_last_page_addr(&last_page, &pageok, 0))
 | |
|           goto err;
 | |
|       }
 | |
|     }
 | |
|     if (logs_found)
 | |
|     {
 | |
|       uint32 i;
 | |
|       log_descriptor.min_file= translog_first_file(log_descriptor.horizon, 1);
 | |
|       log_descriptor.max_file= last_logno;
 | |
|       /* Open all files */
 | |
|       if (allocate_dynamic(&log_descriptor.open_files,
 | |
|                            log_descriptor.max_file -
 | |
|                            log_descriptor.min_file + 1))
 | |
|         goto err;
 | |
|       for (i = log_descriptor.max_file; i >= log_descriptor.min_file; i--)
 | |
|       {
 | |
|         /*
 | |
|           We can't allocate all file together because they will be freed
 | |
|           one by one
 | |
|         */
 | |
|         TRANSLOG_FILE *file= (TRANSLOG_FILE *)my_malloc(PSI_INSTRUMENT_ME, sizeof(TRANSLOG_FILE),
 | |
|                                                         MYF(0));
 | |
| 
 | |
|         compile_time_assert(MY_FILEPOS_ERROR > 0xffffffffULL);
 | |
|         if (file == NULL ||
 | |
|             (file->handler.file=
 | |
|              open_logfile_by_number_no_cache(i)) < 0 ||
 | |
|             mysql_file_seek(file->handler.file, 0, SEEK_END, MYF(0)) >=
 | |
|             0xffffffffULL)
 | |
|         {
 | |
|           int j;
 | |
|           for (j= i - log_descriptor.min_file - 1; j > 0; j--)
 | |
|           {
 | |
|             TRANSLOG_FILE *el=
 | |
|               *dynamic_element(&log_descriptor.open_files, j,
 | |
|                                TRANSLOG_FILE **);
 | |
|             mysql_file_close(el->handler.file, MYF(MY_WME));
 | |
|             my_free(el);
 | |
|           }
 | |
|           if (file)
 | |
|           {
 | |
|             free(file);
 | |
|             goto err;
 | |
|           }
 | |
|           else
 | |
|             goto err;
 | |
|         }
 | |
|         translog_file_init(file, i, 1);
 | |
|         /* we allocated space so it can't fail */
 | |
|         insert_dynamic(&log_descriptor.open_files, (uchar *)&file);
 | |
|       }
 | |
|       DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|                   log_descriptor.open_files.elements);
 | |
|     }
 | |
|   }
 | |
|   else if (readonly)
 | |
|   {
 | |
|     /* There is no logs and there is read-only mode => nothing to read */
 | |
|     DBUG_PRINT("error", ("No logs and read-only mode"));
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   if (logs_found)
 | |
|   {
 | |
|     TRANSLOG_ADDRESS current_page= sure_page;
 | |
|     my_bool pageok;
 | |
| 
 | |
|     DBUG_PRINT("info", ("The log is really present"));
 | |
|     if (sure_page > last_page)
 | |
|     {
 | |
|       my_printf_error(HA_ERR_GENERIC, "Aria engine: log data error\n"
 | |
|                       "last_log_page:   " LSN_FMT " is less than\n"
 | |
|                       "checkpoint page: " LSN_FMT, MYF(0),
 | |
|                       LSN_IN_PARTS(last_page), LSN_IN_PARTS(sure_page));
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     /* TODO: check page size */
 | |
| 
 | |
|     last_valid_page= LSN_IMPOSSIBLE;
 | |
|     /*
 | |
|       Scans and validate pages. We need it to show "outside" only for sure
 | |
|       valid part of the log. If the log was damaged then fixed we have to
 | |
|       cut off damaged part before some other process start write something
 | |
|       in the log.
 | |
|     */
 | |
|     do
 | |
|     {
 | |
|       TRANSLOG_ADDRESS current_file_last_page;
 | |
|       current_file_last_page= current_page;
 | |
|       if (translog_get_last_page_addr(¤t_file_last_page, &pageok, 0))
 | |
|         goto err;
 | |
|       if (!pageok)
 | |
|       {
 | |
|         DBUG_PRINT("error", ("File %lu have no complete last page",
 | |
|                              (ulong) LSN_FILE_NO(current_file_last_page)));
 | |
|         old_log_was_recovered= 1;
 | |
|         /* This file is not written till the end so it should be last */
 | |
|         last_page= current_file_last_page;
 | |
|         /* TODO: issue warning */
 | |
|       }
 | |
|       do
 | |
|       {
 | |
|         TRANSLOG_VALIDATOR_DATA data;
 | |
|         TRANSLOG_PAGE_SIZE_BUFF psize_buff;
 | |
|         uchar *page;
 | |
|         data.addr= ¤t_page;
 | |
|         if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL)
 | |
|           goto err;
 | |
|         if (data.was_recovered)
 | |
|         {
 | |
|           DBUG_PRINT("error", ("file no: %lu (%d)  "
 | |
|                                "rec_offset: 0x%lx (%lu) (%d)",
 | |
|                                (ulong) LSN_FILE_NO(current_page),
 | |
|                                (uint3korr(page + 3) !=
 | |
|                                 LSN_FILE_NO(current_page)),
 | |
|                                (ulong) LSN_OFFSET(current_page),
 | |
|                                (ulong) (LSN_OFFSET(current_page) /
 | |
|                                         TRANSLOG_PAGE_SIZE),
 | |
|                                (uint3korr(page) !=
 | |
|                                 LSN_OFFSET(current_page) /
 | |
|                                 TRANSLOG_PAGE_SIZE)));
 | |
|           old_log_was_recovered= 1;
 | |
|           break;
 | |
|         }
 | |
|         old_flags= page[TRANSLOG_PAGE_FLAGS];
 | |
|         last_valid_page= current_page;
 | |
|         current_page+= TRANSLOG_PAGE_SIZE; /* increase offset */
 | |
|       } while (current_page <= current_file_last_page);
 | |
|       current_page+= LSN_ONE_FILE;
 | |
|       current_page= LSN_REPLACE_OFFSET(current_page, TRANSLOG_PAGE_SIZE);
 | |
|     } while (LSN_FILE_NO(current_page) <= LSN_FILE_NO(last_page) &&
 | |
|              !old_log_was_recovered);
 | |
|     if (last_valid_page == LSN_IMPOSSIBLE)
 | |
|     {
 | |
|       /* Panic!!! Even page which should be valid is invalid */
 | |
|       /* TODO: issue error */
 | |
|       goto err;
 | |
|     }
 | |
|     DBUG_PRINT("info", ("Last valid page is in file: %lu  "
 | |
|                         "offset: %lu (0x%lx)  "
 | |
|                         "Logs found: %d  was recovered: %d  "
 | |
|                         "flags match: %d",
 | |
|                         (ulong) LSN_FILE_NO(last_valid_page),
 | |
|                         (ulong) LSN_OFFSET(last_valid_page),
 | |
|                         (ulong) LSN_OFFSET(last_valid_page),
 | |
|                         logs_found, old_log_was_recovered,
 | |
|                         (old_flags == flags)));
 | |
| 
 | |
|     /* TODO: check server ID */
 | |
|     if (logs_found && !old_log_was_recovered && old_flags == flags)
 | |
|     {
 | |
|       TRANSLOG_VALIDATOR_DATA data;
 | |
|       TRANSLOG_PAGE_SIZE_BUFF psize_buff;
 | |
|       uchar *page;
 | |
|       uint16 chunk_offset;
 | |
|       data.addr= &last_valid_page;
 | |
|       /* continue old log */
 | |
|       DBUG_ASSERT(LSN_FILE_NO(last_valid_page)==
 | |
|                   LSN_FILE_NO(log_descriptor.horizon));
 | |
|       if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL ||
 | |
|           (chunk_offset= translog_get_first_chunk_offset(page)) == 0)
 | |
|         goto err;
 | |
| 
 | |
|       /* Puts filled part of old page in the buffer */
 | |
|       log_descriptor.horizon= last_valid_page;
 | |
|       translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0);
 | |
|       /*
 | |
|          Free space if filled with TRANSLOG_FILLER and first uchar of
 | |
|          real chunk can't be TRANSLOG_FILLER
 | |
|       */
 | |
|       while (chunk_offset < TRANSLOG_PAGE_SIZE &&
 | |
|              page[chunk_offset] != TRANSLOG_FILLER)
 | |
|       {
 | |
|         uint16 chunk_length;
 | |
|         if ((chunk_length=
 | |
|              translog_get_total_chunk_length(page, chunk_offset)) == 0)
 | |
|           goto err;
 | |
|         DBUG_PRINT("info", ("chunk: offset: %u  length: %u",
 | |
|                             (uint) chunk_offset, (uint) chunk_length));
 | |
|         chunk_offset+= chunk_length;
 | |
| 
 | |
|         /* chunk can't cross the page border */
 | |
|         DBUG_ASSERT(chunk_offset <= TRANSLOG_PAGE_SIZE);
 | |
|       }
 | |
|       memcpy(log_descriptor.buffers->buffer, page, chunk_offset);
 | |
|       log_descriptor.bc.buffer->size+= chunk_offset;
 | |
|       log_descriptor.bc.ptr+= chunk_offset;
 | |
|       log_descriptor.bc.current_page_fill= chunk_offset;
 | |
|       log_descriptor.horizon= LSN_REPLACE_OFFSET(log_descriptor.horizon,
 | |
|                                                  (chunk_offset +
 | |
|                                                   LSN_OFFSET(last_valid_page)));
 | |
|       DBUG_PRINT("info", ("Move Page #%u: %p  chaser: %d  Size: %lu (%lu)",
 | |
|                           (uint) log_descriptor.bc.buffer_no,
 | |
|                           log_descriptor.bc.buffer,
 | |
|                           log_descriptor.bc.chaser,
 | |
|                           (ulong) log_descriptor.bc.buffer->size,
 | |
|                           (ulong) (log_descriptor.bc.ptr - log_descriptor.bc.
 | |
|                                    buffer->buffer)));
 | |
|       translog_check_cursor(&log_descriptor.bc);
 | |
|     }
 | |
|     if (!old_log_was_recovered && old_flags == flags)
 | |
|     {
 | |
|       LOGHANDLER_FILE_INFO info;
 | |
| 
 | |
|       /*
 | |
|         Accessing &log_descriptor.open_files without mutex is safe
 | |
|         because it is initialization
 | |
|       */
 | |
|       if (translog_read_file_header(&info,
 | |
|                                     (*dynamic_element(&log_descriptor.
 | |
|                                                       open_files,
 | |
|                                                       0, TRANSLOG_FILE **))->
 | |
|                                     handler.file))
 | |
|         goto err;
 | |
|       version_changed= (info.maria_version != TRANSLOG_VERSION_ID);
 | |
|     }
 | |
|   }
 | |
|   DBUG_PRINT("info", ("Logs found: %d  was recovered: %d",
 | |
|                       logs_found, old_log_was_recovered));
 | |
|   if (!logs_found)
 | |
|   {
 | |
|     TRANSLOG_FILE *file= (TRANSLOG_FILE*)my_malloc(PSI_INSTRUMENT_ME,
 | |
|                                            sizeof(TRANSLOG_FILE), MYF(MY_WME));
 | |
|     DBUG_PRINT("info", ("The log is not found => we will create new log"));
 | |
|     if (file == NULL)
 | |
|        goto err;
 | |
|     /* Start new log system from scratch */
 | |
|     log_descriptor.horizon= MAKE_LSN(start_file_num,
 | |
|                                      TRANSLOG_PAGE_SIZE); /* header page */
 | |
|     translog_file_init(file, start_file_num, 0);
 | |
|     if (insert_dynamic(&log_descriptor.open_files, (uchar*)&file))
 | |
|     {
 | |
|       my_free(file);
 | |
|       goto err;
 | |
|     }
 | |
|     if ((file->handler.file=
 | |
|          create_logfile_by_number_no_cache(start_file_num)) == -1)
 | |
|       goto err;
 | |
|     log_descriptor.min_file= log_descriptor.max_file= start_file_num;
 | |
|     if (translog_write_file_header())
 | |
|       goto err;
 | |
|     DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|                 log_descriptor.open_files.elements);
 | |
| 
 | |
|     if (ma_control_file_write_and_force(checkpoint_lsn, start_file_num,
 | |
|                                         max_trid_in_control_file,
 | |
|                                         recovery_failures))
 | |
|       goto err;
 | |
|     /* assign buffer 0 */
 | |
|     translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0);
 | |
|     translog_new_page_header(&log_descriptor.horizon, &log_descriptor.bc);
 | |
|   }
 | |
|   else if ((old_log_was_recovered || old_flags != flags || version_changed) &&
 | |
|            !readonly)
 | |
|   {
 | |
|     /* leave the damaged file untouched */
 | |
|     log_descriptor.horizon+= LSN_ONE_FILE;
 | |
|     /* header page */
 | |
|     log_descriptor.horizon= LSN_REPLACE_OFFSET(log_descriptor.horizon,
 | |
|                                                TRANSLOG_PAGE_SIZE);
 | |
|     if (translog_create_new_file())
 | |
|       goto err;
 | |
|     /*
 | |
|       Buffer system left untouched after recovery => we should init it
 | |
|       (starting from buffer 0)
 | |
|     */
 | |
|     translog_start_buffer(log_descriptor.buffers, &log_descriptor.bc, 0);
 | |
|     translog_new_page_header(&log_descriptor.horizon, &log_descriptor.bc);
 | |
|   }
 | |
| 
 | |
|   /* all LSNs that are on disk are flushed */
 | |
|   log_descriptor.log_start= log_descriptor.sent_to_disk=
 | |
|     log_descriptor.flushed= log_descriptor.horizon;
 | |
|   log_descriptor.in_buffers_only= log_descriptor.bc.buffer->offset;
 | |
|   log_descriptor.max_lsn= LSN_IMPOSSIBLE; /* set to 0 */
 | |
|   /*
 | |
|     Now 'flushed' is set to 'horizon' value, but 'horizon' is (potentially)
 | |
|     address of the next LSN and we want indicate that all LSNs that are
 | |
|     already on the disk are flushed so we need decrease horizon on 1 (we are
 | |
|     sure that there is no LSN on the disk which is greater then 'flushed'
 | |
|     and there will not be LSN created that is equal or less then the value
 | |
|     of the 'flushed').
 | |
|   */
 | |
|   log_descriptor.flushed--; /* offset decreased */
 | |
|   log_descriptor.sent_to_disk--; /* offset decreased */
 | |
|   /*
 | |
|     Log records will refer to a MARIA_SHARE by a unique 2-byte id; set up
 | |
|     structures for generating 2-byte ids:
 | |
|   */
 | |
|   id_to_share= (MARIA_SHARE **) my_malloc(PSI_INSTRUMENT_ME, SHARE_ID_MAX * sizeof(MARIA_SHARE*),
 | |
|                                           MYF(MY_WME | MY_ZEROFILL));
 | |
|   if (unlikely(!id_to_share))
 | |
|     goto err;
 | |
|   id_to_share--; /* min id is 1 */
 | |
| 
 | |
|   /* Check the last LSN record integrity */
 | |
|   if (logs_found)
 | |
|   {
 | |
|     TRANSLOG_SCANNER_DATA scanner;
 | |
|     TRANSLOG_ADDRESS page_addr;
 | |
|     LSN last_lsn= LSN_IMPOSSIBLE;
 | |
|     /*
 | |
|       take very last page address and try to find LSN record on it
 | |
|       if it fail take address of previous page and so on
 | |
|     */
 | |
|     page_addr= (log_descriptor.horizon -
 | |
|                 ((log_descriptor.horizon - 1) % TRANSLOG_PAGE_SIZE + 1));
 | |
|     if (translog_scanner_init(page_addr, 1, &scanner, 1))
 | |
|       goto err;
 | |
|     scanner.page_offset= page_overhead[scanner.page[TRANSLOG_PAGE_FLAGS]];
 | |
|     for (;;)
 | |
|     {
 | |
|       uint chunk_1byte;
 | |
|       chunk_1byte= scanner.page[scanner.page_offset];
 | |
|       while (!translog_is_LSN_chunk(chunk_1byte) &&
 | |
|              scanner.page != END_OF_LOG &&
 | |
|              scanner.page[scanner.page_offset] != TRANSLOG_FILLER &&
 | |
|              scanner.page_addr == page_addr)
 | |
|       {
 | |
|         if (translog_get_next_chunk(&scanner))
 | |
|         {
 | |
|           translog_destroy_scanner(&scanner);
 | |
|           goto err;
 | |
|         }
 | |
|         if (scanner.page != END_OF_LOG)
 | |
|           chunk_1byte= scanner.page[scanner.page_offset];
 | |
|       }
 | |
|       if (translog_is_LSN_chunk(chunk_1byte))
 | |
|       {
 | |
|         last_lsn= scanner.page_addr + scanner.page_offset;
 | |
|         if (translog_get_next_chunk(&scanner))
 | |
|         {
 | |
|           translog_destroy_scanner(&scanner);
 | |
|           goto err;
 | |
|         }
 | |
|         if (scanner.page == END_OF_LOG)
 | |
|           break; /* it was the last record */
 | |
|         chunk_1byte= scanner.page[scanner.page_offset];
 | |
|         continue; /* try to find other record on this page */
 | |
|       }
 | |
| 
 | |
|       if (last_lsn != LSN_IMPOSSIBLE)
 | |
|         break; /* there is no more records on the page */
 | |
| 
 | |
|       /* We have to make step back */
 | |
|       if (unlikely(LSN_OFFSET(page_addr) == TRANSLOG_PAGE_SIZE))
 | |
|       {
 | |
|         uint32 file_no= LSN_FILE_NO(page_addr);
 | |
|         my_bool last_page_ok;
 | |
|         /* it is beginning of the current file */
 | |
|         if (unlikely(file_no == 1))
 | |
|         {
 | |
|           /*
 | |
|             It is beginning of the log => there is no LSNs in the log =>
 | |
|             There is no harm in leaving it "as-is".
 | |
|           */
 | |
|           log_descriptor.previous_flush_horizon= log_descriptor.horizon;
 | |
|           DBUG_PRINT("info", ("previous_flush_horizon: " LSN_FMT,
 | |
|                               LSN_IN_PARTS(log_descriptor.
 | |
|                                            previous_flush_horizon)));
 | |
|           DBUG_RETURN(0);
 | |
|         }
 | |
|         file_no--;
 | |
|         page_addr= MAKE_LSN(file_no, TRANSLOG_PAGE_SIZE);
 | |
|         translog_get_last_page_addr(&page_addr, &last_page_ok, 0);
 | |
|         /* page should be OK as it is not the last file */
 | |
|         DBUG_ASSERT(last_page_ok);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          page_addr-= TRANSLOG_PAGE_SIZE;
 | |
|       }
 | |
|       translog_destroy_scanner(&scanner);
 | |
|       if (translog_scanner_init(page_addr, 1, &scanner, 1))
 | |
|         goto err;
 | |
|       scanner.page_offset= page_overhead[scanner.page[TRANSLOG_PAGE_FLAGS]];
 | |
|     }
 | |
|     translog_destroy_scanner(&scanner);
 | |
| 
 | |
|     /* Now scanner points to the last LSN chunk, lets check it */
 | |
|     {
 | |
|       TRANSLOG_HEADER_BUFFER rec;
 | |
|       translog_size_t rec_len;
 | |
|       int len;
 | |
|       uchar buffer[1];
 | |
|       DBUG_PRINT("info", ("going to check the last found record " LSN_FMT,
 | |
|                           LSN_IN_PARTS(last_lsn)));
 | |
| 
 | |
|       len=
 | |
|         translog_read_record_header(last_lsn, &rec);
 | |
|       if (unlikely (len == RECHEADER_READ_ERROR ||
 | |
|                     len == RECHEADER_READ_EOF))
 | |
|       {
 | |
|         DBUG_PRINT("error", ("unexpected end of log or record during "
 | |
|                              "reading record header: " LSN_FMT "  len: %d",
 | |
|                              LSN_IN_PARTS(last_lsn), len));
 | |
|         if (readonly)
 | |
|           log_descriptor.log_start= log_descriptor.horizon= last_lsn;
 | |
|         else if (translog_truncate_log(last_lsn))
 | |
|         {
 | |
|           translog_free_record_header(&rec);
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         DBUG_ASSERT(last_lsn == rec.lsn);
 | |
|         if (likely(rec.record_length != 0))
 | |
|         {
 | |
|           /*
 | |
|             Reading the last byte of record will trigger scanning all
 | |
|             record chunks for now
 | |
|           */
 | |
|           rec_len= translog_read_record(rec.lsn, rec.record_length - 1, 1,
 | |
|                                         buffer, NULL);
 | |
|           if (rec_len != 1)
 | |
|           {
 | |
|             DBUG_PRINT("error", ("unexpected end of log or record during "
 | |
|                                  "reading record body: " LSN_FMT "  len: %d",
 | |
|                                  LSN_IN_PARTS(rec.lsn),
 | |
|                                  len));
 | |
|             if (readonly)
 | |
|               log_descriptor.log_start= log_descriptor.horizon= last_lsn;
 | |
| 
 | |
|             else if (translog_truncate_log(last_lsn))
 | |
|             {
 | |
|               translog_free_record_header(&rec);
 | |
|               goto err;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       translog_free_record_header(&rec);
 | |
|     }
 | |
|   }
 | |
|   log_descriptor.previous_flush_horizon= log_descriptor.horizon;
 | |
|   DBUG_PRINT("info", ("previous_flush_horizon: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(log_descriptor.previous_flush_horizon)));
 | |
|   DBUG_RETURN(0);
 | |
| err:
 | |
|   ma_message_no_user(0, "log initialization failed");
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| PRAGMA_REENABLE_CHECK_STACK_FRAME
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Free transaction log file buffer.
 | |
| 
 | |
|   @param buffer_no       The buffer to free
 | |
| */
 | |
| 
 | |
| static void translog_buffer_destroy(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_buffer_destroy");
 | |
|   DBUG_PRINT("enter",
 | |
|              ("Buffer #%u: %p  file: %d  offset: " LSN_FMT "  size: %lu",
 | |
|               (uint) buffer->buffer_no, buffer,
 | |
|               (buffer->file ? buffer->file->handler.file : -1),
 | |
|               LSN_IN_PARTS(buffer->offset),
 | |
|               (ulong) buffer->size));
 | |
|   if (buffer->file != NULL)
 | |
|   {
 | |
|     /*
 | |
|       We ignore errors here, because we can't do something about it
 | |
|       (it is shutting down)
 | |
| 
 | |
|       We also have to take the locks even if there can't be any other
 | |
|       threads running, because translog_buffer_flush()
 | |
|       requires that we have the buffer locked.
 | |
|     */
 | |
|     translog_buffer_lock(buffer);
 | |
|     translog_buffer_flush(buffer);
 | |
|     translog_buffer_unlock(buffer);
 | |
|   }
 | |
|   DBUG_PRINT("info", ("Destroy mutex: %p",  &buffer->mutex));
 | |
|   mysql_mutex_destroy(&buffer->mutex);
 | |
|   mysql_cond_destroy(&buffer->waiting_filling_buffer);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Free log handler resources
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_destroy()
 | |
| */
 | |
| 
 | |
| void translog_destroy()
 | |
| {
 | |
|   TRANSLOG_FILE **file;
 | |
|   uint i;
 | |
|   uint8 current_buffer;
 | |
|   DBUG_ENTER("translog_destroy");
 | |
| 
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
|   translog_lock();
 | |
|   current_buffer= log_descriptor.bc.buffer_no;
 | |
|   translog_status= (translog_status == TRANSLOG_READONLY ?
 | |
|                     TRANSLOG_UNINITED :
 | |
|                     TRANSLOG_SHUTDOWN);
 | |
|   if (log_descriptor.bc.buffer->file != NULL)
 | |
|     translog_finish_page(&log_descriptor.horizon, &log_descriptor.bc);
 | |
|   translog_unlock();
 | |
| 
 | |
|   for (i= 0; i < TRANSLOG_BUFFERS_NO; i++)
 | |
|   {
 | |
|     struct st_translog_buffer *buffer= (log_descriptor.buffers +
 | |
|                                         ((i + current_buffer + 1) %
 | |
|                                          TRANSLOG_BUFFERS_NO));
 | |
|     translog_buffer_destroy(buffer);
 | |
|   }
 | |
|   translog_status= TRANSLOG_UNINITED;
 | |
| 
 | |
|   /* close files */
 | |
|   while ((file= (TRANSLOG_FILE **)pop_dynamic(&log_descriptor.open_files)))
 | |
|     translog_close_log_file(*file);
 | |
|   mysql_mutex_destroy(&log_descriptor.sent_to_disk_lock);
 | |
|   mysql_mutex_destroy(&log_descriptor.file_header_lock);
 | |
|   mysql_mutex_destroy(&log_descriptor.unfinished_files_lock);
 | |
|   mysql_mutex_destroy(&log_descriptor.purger_lock);
 | |
|   mysql_mutex_destroy(&log_descriptor.log_flush_lock);
 | |
|   mysql_mutex_destroy(&log_descriptor.dirty_buffer_mask_lock);
 | |
|   mysql_cond_destroy(&log_descriptor.log_flush_cond);
 | |
|   mysql_cond_destroy(&log_descriptor.new_goal_cond);
 | |
|   mysql_rwlock_destroy(&log_descriptor.open_files_lock);
 | |
|   delete_dynamic(&log_descriptor.open_files);
 | |
|   delete_dynamic(&log_descriptor.unfinished_files);
 | |
| 
 | |
|   if (log_descriptor.directory_fd >= 0)
 | |
|     mysql_file_close(log_descriptor.directory_fd, MYF(MY_WME));
 | |
|   if (id_to_share != NULL)
 | |
|     my_free(id_to_share + 1);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Starts new page.
 | |
| 
 | |
|   @param horizon         \ Position in file and buffer where we are
 | |
|   @param cursor          /
 | |
|   @param prev_buffer     Buffer which should be flushed will be assigned here.
 | |
|                          This is always set (to NULL if nothing to flush).
 | |
| 
 | |
|   @note We do not want to flush the buffer immediately because we want to
 | |
|   let caller of this function first advance 'horizon' pointer and unlock the
 | |
|   loghandler and only then flush the log which can take some time.
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_page_next(TRANSLOG_ADDRESS *horizon,
 | |
|                                   struct st_buffer_cursor *cursor,
 | |
|                                   struct st_translog_buffer **prev_buffer)
 | |
| {
 | |
|   struct st_translog_buffer *buffer= cursor->buffer;
 | |
|   DBUG_ENTER("translog_page_next");
 | |
| 
 | |
|   *prev_buffer= NULL;
 | |
|   if ((cursor->ptr + TRANSLOG_PAGE_SIZE >
 | |
|        cursor->buffer->buffer + TRANSLOG_WRITE_BUFFER) ||
 | |
|       (LSN_OFFSET(*horizon) >
 | |
|        log_descriptor.log_file_max_size - TRANSLOG_PAGE_SIZE))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Switch to next buffer  Buffer Size: %lu (%lu) => %d  "
 | |
|                         "File size: %lu  max: %lu => %d",
 | |
|                         (ulong) cursor->buffer->size,
 | |
|                         (ulong) (cursor->ptr - cursor->buffer->buffer),
 | |
|                         (cursor->ptr + TRANSLOG_PAGE_SIZE >
 | |
|                          cursor->buffer->buffer + TRANSLOG_WRITE_BUFFER),
 | |
|                         (ulong) LSN_OFFSET(*horizon),
 | |
|                         (ulong) log_descriptor.log_file_max_size,
 | |
|                         (LSN_OFFSET(*horizon) >
 | |
|                          (log_descriptor.log_file_max_size -
 | |
|                           TRANSLOG_PAGE_SIZE))));
 | |
|     if (translog_buffer_next(horizon, cursor,
 | |
|                              LSN_OFFSET(*horizon) >
 | |
|                              (log_descriptor.log_file_max_size -
 | |
|                               TRANSLOG_PAGE_SIZE)))
 | |
|       DBUG_RETURN(1);
 | |
|     *prev_buffer= buffer;
 | |
|     DBUG_PRINT("info", ("Buffer #%u (%p): have to be flushed",
 | |
|                         (uint) buffer->buffer_no, buffer));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Use the same buffer #%u (%p): "
 | |
|                         "Buffer Size: %lu (%lu)",
 | |
|                         (uint) buffer->buffer_no,
 | |
|                         buffer,
 | |
|                         (ulong) cursor->buffer->size,
 | |
|                         (ulong) (cursor->ptr - cursor->buffer->buffer)));
 | |
|     translog_finish_page(horizon, cursor);
 | |
|     translog_new_page_header(horizon, cursor);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Write data of given length to the current page
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_data_on_page()
 | |
|     horizon              \ Pointers on file and buffer
 | |
|     cursor               /
 | |
|     length               IN     length of the chunk
 | |
|     buffer               buffer with data
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_write_data_on_page(TRANSLOG_ADDRESS *horizon,
 | |
|                                            struct st_buffer_cursor *cursor,
 | |
|                                            translog_size_t length,
 | |
|                                            uchar *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_write_data_on_page");
 | |
|   DBUG_PRINT("enter", ("Chunk length: %lu  Page size %u",
 | |
|                        (ulong) length, (uint) cursor->current_page_fill));
 | |
|   DBUG_ASSERT(length > 0);
 | |
|   DBUG_ASSERT(length + cursor->current_page_fill <= TRANSLOG_PAGE_SIZE);
 | |
|   DBUG_ASSERT(length + cursor->ptr <= cursor->buffer->buffer +
 | |
|               TRANSLOG_WRITE_BUFFER);
 | |
| 
 | |
|   memcpy(cursor->ptr, buffer, length);
 | |
|   cursor->ptr+= length;
 | |
|   (*horizon)+= length; /* adds offset */
 | |
|   cursor->current_page_fill+= length;
 | |
|   if (!cursor->chaser)
 | |
|     cursor->buffer->size+= length;
 | |
|   DBUG_PRINT("info", ("Write data buffer #%u: %p  "
 | |
|                       "chaser: %d  Size: %lu (%lu)",
 | |
|                       (uint) cursor->buffer->buffer_no, cursor->buffer,
 | |
|                       cursor->chaser, (ulong) cursor->buffer->size,
 | |
|                       (ulong) (cursor->ptr - cursor->buffer->buffer)));
 | |
|   translog_check_cursor(cursor);
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Write data from parts of given length to the current page
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_parts_on_page()
 | |
|     horizon              \ Pointers on file and buffer
 | |
|     cursor               /
 | |
|     length               IN     length of the chunk
 | |
|     parts                IN/OUT chunk source
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_write_parts_on_page(TRANSLOG_ADDRESS *horizon,
 | |
|                                             struct st_buffer_cursor *cursor,
 | |
|                                             translog_size_t length,
 | |
|                                             struct st_translog_parts *parts)
 | |
| {
 | |
|   translog_size_t left= length;
 | |
|   uint cur= (uint) parts->current;
 | |
|   DBUG_ENTER("translog_write_parts_on_page");
 | |
|   DBUG_PRINT("enter", ("Chunk length: %lu  parts: %u of %u. Page size: %u  "
 | |
|                        "Buffer size: %lu (%lu)",
 | |
|                        (ulong) length,
 | |
|                        (uint) (cur + 1), (uint) parts->elements,
 | |
|                        (uint) cursor->current_page_fill,
 | |
|                        (ulong) cursor->buffer->size,
 | |
|                        (ulong) (cursor->ptr - cursor->buffer->buffer)));
 | |
|   DBUG_ASSERT(length > 0);
 | |
|   DBUG_ASSERT(length + cursor->current_page_fill <= TRANSLOG_PAGE_SIZE);
 | |
|   DBUG_ASSERT(length + cursor->ptr <= cursor->buffer->buffer +
 | |
|               TRANSLOG_WRITE_BUFFER);
 | |
| 
 | |
|   do
 | |
|   {
 | |
|     translog_size_t len;
 | |
|     LEX_CUSTRING *part;
 | |
|     const uchar *buff;
 | |
| 
 | |
|     DBUG_ASSERT(cur < parts->elements);
 | |
|     part= parts->parts + cur;
 | |
|     buff= part->str;
 | |
|     DBUG_PRINT("info", ("Part: %u  Length: %lu  left: %lu  buff: %p",
 | |
|                         (uint) (cur + 1), (ulong) part->length, (ulong) left,
 | |
|                         buff));
 | |
| 
 | |
|     if (part->length > left)
 | |
|     {
 | |
|       /* we should write less then the current part */
 | |
|       len= left;
 | |
|       part->length-= len;
 | |
|       part->str+= len;
 | |
|       DBUG_PRINT("info", ("Set new part: %u  Length: %lu",
 | |
|                           (uint) (cur + 1), (ulong) part->length));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       len= (translog_size_t) part->length;
 | |
|       cur++;
 | |
|       DBUG_PRINT("info", ("moved to next part (len: %lu)", (ulong) len));
 | |
|     }
 | |
|     DBUG_PRINT("info", ("copy: %p <- %p  %u",
 | |
|                         cursor->ptr, buff, len));
 | |
|     if (likely(len))
 | |
|     {
 | |
|       memcpy(cursor->ptr, buff, len);
 | |
|       left-= len;
 | |
|       cursor->ptr+= len;
 | |
|     }
 | |
|   } while (left);
 | |
| 
 | |
|   DBUG_PRINT("info", ("Horizon: " LSN_FMT "  Length %u(0x%x)",
 | |
|                       LSN_IN_PARTS(*horizon),
 | |
|                       length, length));
 | |
|   parts->current= cur;
 | |
|   (*horizon)+= length; /* offset increasing */
 | |
|   cursor->current_page_fill+= length;
 | |
|   if (!cursor->chaser)
 | |
|     cursor->buffer->size+= length;
 | |
|   /*
 | |
|     We do not not updating parts->total_record_length here because it is
 | |
|     need only before writing record to have total length
 | |
|   */
 | |
|   DBUG_PRINT("info", ("Write parts buffer #%u: %p  "
 | |
|                       "chaser: %d  Size: %lu (%lu)  "
 | |
|                       "Horizon: " LSN_FMT "  buff offset: 0x%x",
 | |
|                       (uint) cursor->buffer->buffer_no, cursor->buffer,
 | |
|                       cursor->chaser, (ulong) cursor->buffer->size,
 | |
|                       (ulong) (cursor->ptr - cursor->buffer->buffer),
 | |
|                       LSN_IN_PARTS(*horizon),
 | |
|                       (uint) (LSN_OFFSET(cursor->buffer->offset) +
 | |
|                                cursor->buffer->size)));
 | |
|   translog_check_cursor(cursor);
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Put 1 group chunk type 0 header into parts array
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_variable_record_1group_header()
 | |
|     parts                Descriptor of record source parts
 | |
|     type                 The log record type
 | |
|     short_trid           Short transaction ID or 0 if it has no sense
 | |
|     header_length        Calculated header length of chunk type 0
 | |
|     chunk0_header        Buffer for the chunk header writing
 | |
| */
 | |
| 
 | |
| static void
 | |
| translog_write_variable_record_1group_header(struct st_translog_parts *parts,
 | |
|                                              enum translog_record_type type,
 | |
|                                              SHORT_TRANSACTION_ID short_trid,
 | |
|                                              uint16 header_length,
 | |
|                                              uchar *chunk0_header)
 | |
| {
 | |
|   LEX_CUSTRING *part;
 | |
|   DBUG_ASSERT(parts->current != 0);     /* first part is left for header */
 | |
|   part= parts->parts + (--parts->current);
 | |
|   parts->total_record_length+= (translog_size_t) (part->length= header_length);
 | |
|   part->str= chunk0_header;
 | |
|   /* puts chunk type */
 | |
|   *chunk0_header= (uchar) (type | TRANSLOG_CHUNK_LSN);
 | |
|   int2store(chunk0_header + 1, short_trid);
 | |
|   /* puts record length */
 | |
|   translog_write_variable_record_1group_code_len(chunk0_header + 3,
 | |
|                                                  parts->record_length,
 | |
|                                                  header_length);
 | |
|   /* puts 0 as chunk length which indicate 1 group record */
 | |
|   int2store(chunk0_header + header_length - 2, 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Increase number of writers for this buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_buffer_increase_writers()
 | |
|     buffer               target buffer
 | |
| */
 | |
| 
 | |
| static inline void
 | |
| translog_buffer_increase_writers(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_buffer_increase_writers");
 | |
|   translog_buffer_lock_assert_owner(buffer);
 | |
|   buffer->copy_to_buffer_in_progress++;
 | |
|   DBUG_PRINT("info", ("copy_to_buffer_in_progress. Buffer #%u  %p  progress: %d",
 | |
|                       (uint) buffer->buffer_no, buffer,
 | |
|                       buffer->copy_to_buffer_in_progress));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Decrease number of writers for this buffer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_buffer_decrease_writers()
 | |
|     buffer               target buffer
 | |
| */
 | |
| 
 | |
| static void translog_buffer_decrease_writers(struct st_translog_buffer *buffer)
 | |
| {
 | |
|   DBUG_ENTER("translog_buffer_decrease_writers");
 | |
|   translog_buffer_lock_assert_owner(buffer);
 | |
|   buffer->copy_to_buffer_in_progress--;
 | |
|   DBUG_PRINT("info",
 | |
|              ("copy_to_buffer_in_progress. Buffer #%u  %p  progress: %d",
 | |
|               (uint) buffer->buffer_no, buffer,
 | |
|               buffer->copy_to_buffer_in_progress));
 | |
|   if (buffer->copy_to_buffer_in_progress == 0)
 | |
|     mysql_cond_broadcast(&buffer->waiting_filling_buffer);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Skip to the next page for chaser (thread which advanced horizon
 | |
|   pointer and now feeling the buffer)
 | |
| 
 | |
|   @param horizon         \ Pointers on file position and buffer
 | |
|   @param cursor          /
 | |
| 
 | |
|   @retval 1 OK
 | |
|   @retval 0 Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_chaser_page_next(TRANSLOG_ADDRESS *horizon,
 | |
|                                          struct st_buffer_cursor *cursor)
 | |
| {
 | |
|   struct st_translog_buffer *buffer_to_flush;
 | |
|   my_bool rc;
 | |
|   DBUG_ENTER("translog_chaser_page_next");
 | |
|   DBUG_ASSERT(cursor->chaser);
 | |
|   rc= translog_page_next(horizon, cursor, &buffer_to_flush);
 | |
|   if (buffer_to_flush != NULL)
 | |
|   {
 | |
|     translog_buffer_lock(buffer_to_flush);
 | |
|     translog_buffer_decrease_writers(buffer_to_flush);
 | |
|     used_buffs_register_unlock(&cursor->buffs, buffer_to_flush);
 | |
|     if (!rc)
 | |
|       rc= translog_buffer_flush(buffer_to_flush);
 | |
|     translog_buffer_unlock(buffer_to_flush);
 | |
|   }
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Put chunk 2 from new page beginning
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_variable_record_chunk2_page()
 | |
|     parts                Descriptor of record source parts
 | |
|     horizon              \ Pointers on file position and buffer
 | |
|     cursor               /
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_write_variable_record_chunk2_page(struct st_translog_parts *parts,
 | |
|                                            TRANSLOG_ADDRESS *horizon,
 | |
|                                            struct st_buffer_cursor *cursor)
 | |
| {
 | |
|   uchar chunk2_header[1];
 | |
|   DBUG_ENTER("translog_write_variable_record_chunk2_page");
 | |
|   chunk2_header[0]= TRANSLOG_CHUNK_NOHDR;
 | |
| 
 | |
|   if (translog_chaser_page_next(horizon, cursor))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   /* Puts chunk type */
 | |
|   translog_write_data_on_page(horizon, cursor, 1, chunk2_header);
 | |
|   /* Puts chunk body */
 | |
|   translog_write_parts_on_page(horizon, cursor,
 | |
|                                log_descriptor.page_capacity_chunk_2, parts);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Put chunk 3 of requested length in the buffer from new page beginning
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_write_variable_record_chunk3_page()
 | |
|     parts                Descriptor of record source parts
 | |
|     length               Length of this chunk
 | |
|     horizon              \ Pointers on file position and buffer
 | |
|     cursor               /
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_write_variable_record_chunk3_page(struct st_translog_parts *parts,
 | |
|                                            uint16 length,
 | |
|                                            TRANSLOG_ADDRESS *horizon,
 | |
|                                            struct st_buffer_cursor *cursor)
 | |
| {
 | |
|   LEX_CUSTRING *part;
 | |
|   uchar chunk3_header[1 + 2];
 | |
|   DBUG_ENTER("translog_write_variable_record_chunk3_page");
 | |
| 
 | |
|   if (translog_chaser_page_next(horizon, cursor))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   if (length == 0)
 | |
|   {
 | |
|     /* It was call to write page header only (no data for chunk 3) */
 | |
|     DBUG_PRINT("info", ("It is a call to make page header only"));
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(parts->current != 0);       /* first part is left for header */
 | |
|   part= parts->parts + (--parts->current);
 | |
|   parts->total_record_length+= (translog_size_t) (part->length= 1 + 2);
 | |
|   part->str= chunk3_header;
 | |
|   /* Puts chunk type */
 | |
|   *chunk3_header= (uchar) (TRANSLOG_CHUNK_LNGTH);
 | |
|   /* Puts chunk length */
 | |
|   int2store(chunk3_header + 1, length);
 | |
| 
 | |
|   translog_write_parts_on_page(horizon, cursor, length + 1 + 2, parts);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Move log pointer (horizon) on given number pages starting from next page,
 | |
|   and given offset on the last page
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_advance_pointer()
 | |
|     pages                Number of full pages starting from the next one
 | |
|     last_page_data       Plus this data on the last page
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_advance_pointer(int pages, uint16 last_page_data,
 | |
|                                         TRUNSLOG_USED_BUFFERS *buffs)
 | |
| {
 | |
|   translog_size_t last_page_offset= (log_descriptor.page_overhead +
 | |
|                                      last_page_data);
 | |
|   translog_size_t offset= (TRANSLOG_PAGE_SIZE -
 | |
|                            log_descriptor.bc.current_page_fill +
 | |
|                            pages * TRANSLOG_PAGE_SIZE + last_page_offset);
 | |
|   translog_size_t buffer_end_offset, file_end_offset, min_offset;
 | |
|   DBUG_ENTER("translog_advance_pointer");
 | |
|   DBUG_PRINT("enter", ("Pointer:  " LSN_FMT " + %u + %u pages + %u + %u",
 | |
|                        LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                        (uint) (TRANSLOG_PAGE_SIZE -
 | |
|                                log_descriptor.bc.current_page_fill),
 | |
|                        pages, (uint) log_descriptor.page_overhead,
 | |
|                        (uint) last_page_data));
 | |
|   translog_lock_assert_owner();
 | |
| 
 | |
|   used_buffs_init(buffs);
 | |
| 
 | |
|   if (pages == -1)
 | |
|   {
 | |
|     /*
 | |
|       It is special case when we advance the pointer on the same page.
 | |
|       It can happened when we write last part of multi-group record.
 | |
|     */
 | |
|     DBUG_ASSERT(last_page_data + log_descriptor.bc.current_page_fill <=
 | |
|                 TRANSLOG_PAGE_SIZE);
 | |
|     offset= last_page_data;
 | |
|     last_page_offset= log_descriptor.bc.current_page_fill + last_page_data;
 | |
|     goto end;
 | |
|   }
 | |
|   DBUG_PRINT("info", ("last_page_offset %lu", (ulong) last_page_offset));
 | |
|   DBUG_ASSERT(last_page_offset <= TRANSLOG_PAGE_SIZE);
 | |
| 
 | |
|   /*
 | |
|     The loop will be executed 1-3 times. Usually we advance the
 | |
|     pointer to fill only the current buffer (if we have more then 1/2 of
 | |
|     buffer free or 2 buffers (rest of current and all next). In case of
 | |
|     really huge record end where we write last group with "table of
 | |
|     content" of all groups and ignore buffer borders we can occupy
 | |
|     3 buffers.
 | |
|   */
 | |
|   for (;;)
 | |
|   {
 | |
|     uint8 new_buffer_no;
 | |
|     struct st_translog_buffer *new_buffer;
 | |
|     struct st_translog_buffer *old_buffer;
 | |
|     buffer_end_offset= TRANSLOG_WRITE_BUFFER - log_descriptor.bc.buffer->size;
 | |
|     if (likely(log_descriptor.log_file_max_size >=
 | |
|                LSN_OFFSET(log_descriptor.horizon)))
 | |
|       file_end_offset= (log_descriptor.log_file_max_size -
 | |
|                         LSN_OFFSET(log_descriptor.horizon));
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         We already have written more then current file limit allow,
 | |
|         So we will finish this page and start new file
 | |
|       */
 | |
|       file_end_offset= (TRANSLOG_PAGE_SIZE -
 | |
|                         log_descriptor.bc.current_page_fill);
 | |
|     }
 | |
|     DBUG_PRINT("info", ("offset: %u  buffer_end_offs: %u, "
 | |
|                         "file_end_offs:  %u",
 | |
|                         offset, buffer_end_offset,
 | |
|                         file_end_offset));
 | |
|     DBUG_PRINT("info", ("Buff #%u %u (%p) offset 0x%x + size 0x%x = "
 | |
|                         "0x%x (0x%x)",
 | |
|                         log_descriptor.bc.buffer->buffer_no,
 | |
|                         log_descriptor.bc.buffer_no,
 | |
|                         log_descriptor.bc.buffer,
 | |
|                         (uint) LSN_OFFSET(log_descriptor.bc.buffer->offset),
 | |
|                         log_descriptor.bc.buffer->size,
 | |
|                         (uint) (LSN_OFFSET(log_descriptor.bc.buffer->offset) +
 | |
|                                  log_descriptor.bc.buffer->size),
 | |
|                         (uint) LSN_OFFSET(log_descriptor.horizon)));
 | |
|     DBUG_ASSERT(LSN_OFFSET(log_descriptor.bc.buffer->offset) +
 | |
|                 log_descriptor.bc.buffer->size ==
 | |
|                 LSN_OFFSET(log_descriptor.horizon));
 | |
| 
 | |
|     if (offset <= buffer_end_offset && offset <= file_end_offset)
 | |
|       break;
 | |
|     old_buffer= log_descriptor.bc.buffer;
 | |
|     new_buffer_no= (log_descriptor.bc.buffer_no + 1) % TRANSLOG_BUFFERS_NO;
 | |
|     new_buffer= log_descriptor.buffers + new_buffer_no;
 | |
| 
 | |
|     translog_buffer_lock(new_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|     {
 | |
|       TRANSLOG_ADDRESS offset= new_buffer->offset;
 | |
|       TRANSLOG_FILE *file= new_buffer->file;
 | |
|       uint8 ver= new_buffer->ver;
 | |
|       translog_lock_assert_owner();
 | |
| #endif
 | |
|       translog_wait_for_buffer_free(new_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|       /* We keep the handler locked so nobody can start this new buffer */
 | |
|       DBUG_ASSERT((offset == new_buffer->offset && new_buffer->file == NULL &&
 | |
|                    (file == NULL ? ver : (uint8)(ver + 1)) ==
 | |
|                     new_buffer->ver) ||
 | |
|                    translog_status == TRANSLOG_READONLY);
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     min_offset= MY_MIN(buffer_end_offset, file_end_offset);
 | |
|     /* TODO: check is it ptr or size enough */
 | |
|     log_descriptor.bc.buffer->size+= min_offset;
 | |
|     log_descriptor.bc.ptr+= min_offset;
 | |
|     DBUG_PRINT("info", ("NewP buffer #%u: %p  chaser: %d  Size: %lu (%lu)",
 | |
|                         (uint) log_descriptor.bc.buffer->buffer_no,
 | |
|                         log_descriptor.bc.buffer,
 | |
|                         log_descriptor.bc.chaser,
 | |
|                         (ulong) log_descriptor.bc.buffer->size,
 | |
|                         (ulong) (log_descriptor.bc.ptr -log_descriptor.bc.
 | |
|                                  buffer->buffer)));
 | |
|     DBUG_ASSERT((ulong) (log_descriptor.bc.ptr -
 | |
|                          log_descriptor.bc.buffer->buffer) ==
 | |
|                 log_descriptor.bc.buffer->size);
 | |
|     DBUG_ASSERT(log_descriptor.bc.buffer->buffer_no ==
 | |
|                 log_descriptor.bc.buffer_no);
 | |
|     translog_buffer_increase_writers(log_descriptor.bc.buffer);
 | |
|     // register for case of error
 | |
|     used_buffs_add(buffs, log_descriptor.bc.buffer);
 | |
| 
 | |
|     if (file_end_offset <= buffer_end_offset)
 | |
|     {
 | |
|       log_descriptor.horizon+= LSN_ONE_FILE;
 | |
|       log_descriptor.horizon= LSN_REPLACE_OFFSET(log_descriptor.horizon,
 | |
|                                                  TRANSLOG_PAGE_SIZE);
 | |
|       DBUG_PRINT("info", ("New file: %lu",
 | |
|                           (ulong) LSN_FILE_NO(log_descriptor.horizon)));
 | |
|       if (translog_create_new_file())
 | |
|       {
 | |
|         struct st_translog_buffer *ob= log_descriptor.bc.buffer;
 | |
|         translog_buffer_unlock(ob);
 | |
|         used_buffs_urgent_unlock(buffs);
 | |
|         translog_buffer_lock(ob);
 | |
|         DBUG_RETURN(1);
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       DBUG_PRINT("info", ("The same file"));
 | |
|       log_descriptor.horizon+= min_offset; /* offset increasing */
 | |
|     }
 | |
|     translog_start_buffer(new_buffer, &log_descriptor.bc, new_buffer_no);
 | |
|     old_buffer->next_buffer_offset= new_buffer->offset;
 | |
|     new_buffer->prev_buffer_offset= old_buffer->offset;
 | |
|     translog_buffer_unlock(old_buffer);
 | |
|     offset-= min_offset;
 | |
|   }
 | |
|   DBUG_PRINT("info", ("drop write_counter"));
 | |
|   log_descriptor.bc.write_counter= 0;
 | |
|   log_descriptor.bc.previous_offset= 0;
 | |
| end:
 | |
|   log_descriptor.bc.ptr+= offset;
 | |
|   log_descriptor.bc.buffer->size+= offset;
 | |
|   translog_buffer_increase_writers(log_descriptor.bc.buffer);
 | |
|   used_buffs_add(buffs, log_descriptor.bc.buffer);
 | |
|   log_descriptor.horizon+= offset; /* offset increasing */
 | |
|   log_descriptor.bc.current_page_fill= last_page_offset;
 | |
|   DBUG_PRINT("info", ("NewP buffer #%u: %p  chaser: %d  Size: %lu (%lu)  "
 | |
|                       "offset: %u  last page: %u",
 | |
|                       (uint) log_descriptor.bc.buffer->buffer_no,
 | |
|                       log_descriptor.bc.buffer,
 | |
|                       log_descriptor.bc.chaser,
 | |
|                       (ulong) log_descriptor.bc.buffer->size,
 | |
|                       (ulong) (log_descriptor.bc.ptr -
 | |
|                                log_descriptor.bc.buffer->
 | |
|                                buffer), (uint) offset,
 | |
|                       (uint) last_page_offset));
 | |
|   DBUG_PRINT("info",
 | |
|              ("pointer moved to: " LSN_FMT,
 | |
|               LSN_IN_PARTS(log_descriptor.horizon)));
 | |
|   translog_check_cursor(&log_descriptor.bc);
 | |
|   log_descriptor.bc.protected= 0;
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| static void
 | |
| used_buffs_add(TRUNSLOG_USED_BUFFERS *buffs,
 | |
|                 struct st_translog_buffer *buff)
 | |
| {
 | |
|   DBUG_ENTER("used_buffs_add");
 | |
|   DBUG_PRINT("enter", ("ADD buffs: %p unlk %u (%p)  wrt_ptr: %u (%p)"
 | |
|                        "  buff %p (%u)",
 | |
|                        buffs,
 | |
|                        buffs->wrt_ptr, buffs->buff[buffs->wrt_ptr],
 | |
|                        buffs->unlck_ptr, buffs->buff[buffs->unlck_ptr],
 | |
|                        buff, buff->buffer_no));
 | |
|   DBUG_ASSERT(buffs->wrt_ptr < MAX_TRUNSLOG_USED_BUFFERS);
 | |
|   buffs->buff[buffs->wrt_ptr++]= buff;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| static void
 | |
| used_buffs_register_unlock(TRUNSLOG_USED_BUFFERS *buffs,
 | |
|                            struct st_translog_buffer *buff
 | |
|                            __attribute__((unused)) )
 | |
| {
 | |
|   DBUG_ENTER("used_buffs_register_unlock");
 | |
|   DBUG_PRINT("enter", ("SUB buffs: %p unlk %u (%p)  wrt_ptr: %u (%p)"
 | |
|                        "  buff %p (%u)",
 | |
|                        buffs,
 | |
|                        buffs->wrt_ptr, buffs->buff[buffs->wrt_ptr],
 | |
|                        buffs->unlck_ptr, buffs->buff[buffs->unlck_ptr],
 | |
|                        buff, buff->buffer_no));
 | |
|   DBUG_ASSERT(buffs->buff[buffs->unlck_ptr] == buff);
 | |
|   buffs->unlck_ptr++;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| static void used_buffs_urgent_unlock(TRUNSLOG_USED_BUFFERS *buffs)
 | |
| {
 | |
|   uint i;
 | |
|   DBUG_ENTER("used_buffs_urgent_unlock");
 | |
|   translog_lock();
 | |
|   translog_stop_writing();
 | |
|   translog_unlock();
 | |
|   for (i= buffs->unlck_ptr; i < buffs->wrt_ptr; i++)
 | |
|   {
 | |
|     struct st_translog_buffer *buf= buffs->buff[i];
 | |
|     translog_buffer_lock(buf);
 | |
|     translog_buffer_decrease_writers(buf);
 | |
|     translog_buffer_unlock(buf);
 | |
|     buffs->buff[i]= NULL;
 | |
|   }
 | |
|   used_buffs_init(buffs);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Get page rest
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_current_page_rest()
 | |
| 
 | |
|   NOTE loghandler should be locked
 | |
| 
 | |
|   RETURN
 | |
|     number of bytes left on the current page
 | |
| */
 | |
| 
 | |
| static uint translog_get_current_page_rest()
 | |
| {
 | |
|   return (TRANSLOG_PAGE_SIZE - log_descriptor.bc.current_page_fill);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get buffer rest in full pages
 | |
| 
 | |
|   SYNOPSIS
 | |
|      translog_get_current_buffer_rest()
 | |
| 
 | |
|   NOTE loghandler should be locked
 | |
| 
 | |
|   RETURN
 | |
|     number of full pages left on the current buffer
 | |
| */
 | |
| 
 | |
| static uint translog_get_current_buffer_rest()
 | |
| {
 | |
|   return (uint)((log_descriptor.bc.buffer->buffer + TRANSLOG_WRITE_BUFFER -
 | |
|            log_descriptor.bc.ptr) /
 | |
|           TRANSLOG_PAGE_SIZE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Calculate possible group size without first (current) page
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_current_group_size()
 | |
| 
 | |
|   NOTE loghandler should be locked
 | |
| 
 | |
|   RETURN
 | |
|     group size without first (current) page
 | |
| */
 | |
| 
 | |
| static translog_size_t translog_get_current_group_size()
 | |
| {
 | |
|   /* buffer rest in full pages */
 | |
|   translog_size_t buffer_rest= translog_get_current_buffer_rest();
 | |
|   DBUG_ENTER("translog_get_current_group_size");
 | |
|   DBUG_PRINT("info", ("buffer_rest in pages: %u", buffer_rest));
 | |
| 
 | |
|   buffer_rest*= log_descriptor.page_capacity_chunk_2;
 | |
|   /* in case of only half of buffer free we can write this and next buffer */
 | |
|   if (buffer_rest < log_descriptor.half_buffer_capacity_chunk_2)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("buffer_rest: %lu -> add %lu",
 | |
|                         (ulong) buffer_rest,
 | |
|                         (ulong) log_descriptor.buffer_capacity_chunk_2));
 | |
|     buffer_rest+= log_descriptor.buffer_capacity_chunk_2;
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("info", ("buffer_rest: %lu", (ulong) buffer_rest));
 | |
| 
 | |
|   DBUG_RETURN(buffer_rest);
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline void set_lsn(LSN *lsn, LSN value)
 | |
| {
 | |
|   DBUG_ENTER("set_lsn");
 | |
|   translog_lock_assert_owner();
 | |
|   *lsn= value;
 | |
|   /* we generate LSN so something is not flushed in log */
 | |
|   log_descriptor.is_everything_flushed= 0;
 | |
|   DBUG_PRINT("info", ("new LSN appeared: " LSN_FMT, LSN_IN_PARTS(value)));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Write variable record in 1 group.
 | |
| 
 | |
|    @param  lsn             LSN of the record will be written here
 | |
|    @param  type            the log record type
 | |
|    @param  short_trid      Short transaction ID or 0 if it has no sense
 | |
|    @param  parts           Descriptor of record source parts
 | |
|    @param  buffer_to_flush Buffer which have to be flushed if it is not 0
 | |
|    @param  header_length   Calculated header length of chunk type 0
 | |
|    @param  trn             Transaction structure pointer for hooks by
 | |
|                            record log type, for short_id
 | |
|    @param  hook_arg        Argument which will be passed to pre-write and
 | |
|                            in-write hooks of this record.
 | |
| 
 | |
|    @note
 | |
|      We must have a translog_lock() when entering this function
 | |
|      We must have buffer_to_flush locked (if not null)
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_write_variable_record_1group(LSN *lsn,
 | |
|                                       enum translog_record_type type,
 | |
|                                       MARIA_HA *tbl_info,
 | |
|                                       SHORT_TRANSACTION_ID short_trid,
 | |
|                                       struct st_translog_parts *parts,
 | |
|                                       struct st_translog_buffer
 | |
|                                       *buffer_to_flush, uint16 header_length,
 | |
|                                       TRN *trn, void *hook_arg)
 | |
| {
 | |
|   TRANSLOG_ADDRESS horizon;
 | |
|   struct st_buffer_cursor cursor;
 | |
|   int rc= 0;
 | |
|   uint i;
 | |
|   translog_size_t record_rest, full_pages, first_page;
 | |
|   uint additional_chunk3_page= 0;
 | |
|   uchar chunk0_header[1 + 2 + 5 + 2];
 | |
|   DBUG_ENTER("translog_write_variable_record_1group");
 | |
|   translog_lock_assert_owner();
 | |
|   if (buffer_to_flush)
 | |
|     translog_buffer_lock_assert_owner(buffer_to_flush);
 | |
| 
 | |
|   set_lsn(lsn, horizon= log_descriptor.horizon);
 | |
|   if (translog_set_lsn_for_files(LSN_FILE_NO(*lsn), LSN_FILE_NO(*lsn),
 | |
|                                  *lsn, TRUE) ||
 | |
|       (log_record_type_descriptor[type].inwrite_hook &&
 | |
|        (*log_record_type_descriptor[type].inwrite_hook)(type, trn, tbl_info,
 | |
|                                                         lsn, hook_arg)))
 | |
|   {
 | |
|     translog_unlock();
 | |
|     if (buffer_to_flush != NULL)
 | |
|     {
 | |
|       translog_buffer_flush(buffer_to_flush);
 | |
|       translog_buffer_unlock(buffer_to_flush);
 | |
|     }
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   cursor= log_descriptor.bc;
 | |
|   cursor.chaser= 1;
 | |
| 
 | |
|   /* Advance pointer to be able unlock the loghandler */
 | |
|   first_page= translog_get_current_page_rest();
 | |
|   record_rest= parts->record_length - (first_page - header_length);
 | |
|   full_pages= record_rest / log_descriptor.page_capacity_chunk_2;
 | |
|   record_rest= (record_rest % log_descriptor.page_capacity_chunk_2);
 | |
| 
 | |
|   if (record_rest + 1 == log_descriptor.page_capacity_chunk_2)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("2 chunks type 3 is needed"));
 | |
|     /* We will write 2 chunks type 3 at the end of this group */
 | |
|     additional_chunk3_page= 1;
 | |
|     record_rest= 1;
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("info", ("first_page: %u (%u)  full_pages: %u (%lu)  "
 | |
|                       "additional: %u (%u)  rest %u = %u",
 | |
|                       first_page, first_page - header_length,
 | |
|                       full_pages,
 | |
|                       (ulong) full_pages *
 | |
|                       log_descriptor.page_capacity_chunk_2,
 | |
|                       additional_chunk3_page,
 | |
|                       additional_chunk3_page *
 | |
|                       (log_descriptor.page_capacity_chunk_2 - 1),
 | |
|                       record_rest, parts->record_length));
 | |
|   /* record_rest + 3 is chunk type 3 overhead + record_rest */
 | |
|   rc= translog_advance_pointer((int)(full_pages + additional_chunk3_page),
 | |
|                                (record_rest ? record_rest + 3 : 0),
 | |
|                                &cursor.buffs);
 | |
|   log_descriptor.bc.buffer->last_lsn= *lsn;
 | |
|   DBUG_PRINT("info", ("last_lsn set to " LSN_FMT "  buffer: %p",
 | |
|                       LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn),
 | |
|                       log_descriptor.bc.buffer));
 | |
| 
 | |
|   translog_unlock();
 | |
| 
 | |
|   /*
 | |
|      Check if we switched buffer and need process it (current buffer is
 | |
|      unlocked already => we will not delay other threads
 | |
|   */
 | |
|   if (buffer_to_flush != NULL)
 | |
|   {
 | |
|     if (!rc)
 | |
|       rc= translog_buffer_flush(buffer_to_flush);
 | |
|     translog_buffer_unlock(buffer_to_flush);
 | |
|   }
 | |
|   if (rc)
 | |
|   {
 | |
|     //translog_advance_pointer decreased writers so it is OK
 | |
|     DBUG_ASSERT(cursor.buffs.unlck_ptr == cursor.buffs.wrt_ptr);
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   translog_write_variable_record_1group_header(parts, type, short_trid,
 | |
|                                                header_length, chunk0_header);
 | |
| 
 | |
|   /* fill the pages */
 | |
|   translog_write_parts_on_page(&horizon, &cursor, first_page, parts);
 | |
| 
 | |
|   DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                       LSN_IN_PARTS(horizon)));
 | |
| 
 | |
|   for (i= 0; i < full_pages; i++)
 | |
|   {
 | |
|     if (translog_write_variable_record_chunk2_page(parts, &horizon, &cursor))
 | |
|       goto error;
 | |
| 
 | |
|     DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT,
 | |
|                         LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                         LSN_IN_PARTS(horizon)));
 | |
|   }
 | |
| 
 | |
|   if (additional_chunk3_page)
 | |
|   {
 | |
|     if (translog_write_variable_record_chunk3_page(parts,
 | |
|                                                    log_descriptor.
 | |
|                                                    page_capacity_chunk_2 - 2,
 | |
|                                                    &horizon, &cursor))
 | |
|       goto error;
 | |
|     DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT,
 | |
|                         LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                         LSN_IN_PARTS(horizon)));
 | |
|     DBUG_ASSERT(cursor.current_page_fill == TRANSLOG_PAGE_SIZE);
 | |
|   }
 | |
| 
 | |
|   if (translog_write_variable_record_chunk3_page(parts,
 | |
|                                                  record_rest,
 | |
|                                                  &horizon, &cursor))
 | |
|     goto error;
 | |
|   DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT,
 | |
|                       (uint) LSN_FILE_NO(log_descriptor.horizon),
 | |
|                       (uint) LSN_OFFSET(log_descriptor.horizon),
 | |
|                       (uint) LSN_FILE_NO(horizon),
 | |
|                       (uint) LSN_OFFSET(horizon)));
 | |
| 
 | |
|   translog_buffer_lock(cursor.buffer);
 | |
|   translog_buffer_decrease_writers(cursor.buffer);
 | |
|   used_buffs_register_unlock(&cursor.buffs, cursor.buffer);
 | |
|   translog_buffer_unlock(cursor.buffer);
 | |
|   DBUG_ASSERT(cursor.buffs.unlck_ptr == cursor.buffs.wrt_ptr);
 | |
|   DBUG_RETURN(0);
 | |
| error:
 | |
|   used_buffs_urgent_unlock(&cursor.buffs);
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Write variable record in 1 chunk.
 | |
| 
 | |
|    @param  lsn             LSN of the record will be written here
 | |
|    @param  type            the log record type
 | |
|    @param  short_trid      Short transaction ID or 0 if it has no sense
 | |
|    @param  parts           Descriptor of record source parts
 | |
|    @param  buffer_to_flush Buffer which have to be flushed if it is not 0
 | |
|    @param  header_length   Calculated header length of chunk type 0
 | |
|    @param  trn             Transaction structure pointer for hooks by
 | |
|                            record log type, for short_id
 | |
|    @param  hook_arg        Argument which will be passed to pre-write and
 | |
|                            in-write hooks of this record.
 | |
| 
 | |
|    @note
 | |
|      We must have a translog_lock() when entering this function
 | |
|      We must have buffer_to_flush locked (if not null)
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_write_variable_record_1chunk(LSN *lsn,
 | |
|                                       enum translog_record_type type,
 | |
|                                       MARIA_HA *tbl_info,
 | |
|                                       SHORT_TRANSACTION_ID short_trid,
 | |
|                                       struct st_translog_parts *parts,
 | |
|                                       struct st_translog_buffer
 | |
|                                       *buffer_to_flush, uint16 header_length,
 | |
|                                       TRN *trn, void *hook_arg)
 | |
| {
 | |
|   int rc;
 | |
|   uchar chunk0_header[1 + 2 + 5 + 2];
 | |
|   DBUG_ENTER("translog_write_variable_record_1chunk");
 | |
|   translog_lock_assert_owner();
 | |
|   if (buffer_to_flush)
 | |
|     translog_buffer_lock_assert_owner(buffer_to_flush);
 | |
| 
 | |
|   translog_write_variable_record_1group_header(parts, type, short_trid,
 | |
|                                                header_length, chunk0_header);
 | |
|   set_lsn(lsn, log_descriptor.horizon);
 | |
|   if (translog_set_lsn_for_files(LSN_FILE_NO(*lsn), LSN_FILE_NO(*lsn),
 | |
|                                  *lsn, TRUE) ||
 | |
|       (log_record_type_descriptor[type].inwrite_hook &&
 | |
|        (*log_record_type_descriptor[type].inwrite_hook)(type, trn, tbl_info,
 | |
|                                                         lsn, hook_arg)))
 | |
|   {
 | |
|     translog_unlock();
 | |
|     rc= 1;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   rc= translog_write_parts_on_page(&log_descriptor.horizon,
 | |
|                                    &log_descriptor.bc,
 | |
|                                    parts->total_record_length, parts);
 | |
|   log_descriptor.bc.buffer->last_lsn= *lsn;
 | |
|   DBUG_PRINT("info", ("last_lsn set to " LSN_FMT "  buffer: %p",
 | |
|                       LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn),
 | |
|                       log_descriptor.bc.buffer));
 | |
|   translog_unlock();
 | |
| 
 | |
|   /*
 | |
|      check if we switched buffer and need process it (current buffer is
 | |
|      unlocked already => we will not delay other threads
 | |
|   */
 | |
| err:
 | |
|   if (buffer_to_flush != NULL)
 | |
|   {
 | |
|     if (!rc)
 | |
|       rc= translog_buffer_flush(buffer_to_flush);
 | |
|     translog_buffer_unlock(buffer_to_flush);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Calculates and write LSN difference (compressed LSN).
 | |
| 
 | |
|   @param base_lsn        LSN from which we calculate difference
 | |
|   @param lsn             LSN for codding
 | |
|   @param dst             Result will be written to dst[-pack_length] .. dst[-1]
 | |
| 
 | |
|   @note To store an LSN in a compact way we will use the following compression:
 | |
|     If a log record has LSN1, and it contains the LSN2 as a back reference,
 | |
|     Instead of LSN2 we write LSN1-LSN2, encoded as:
 | |
|      two bits     the number N (see below)
 | |
|      14 bits
 | |
|      N bytes
 | |
|      That is, LSN is encoded in 2..5 bytes, and the number of bytes minus 2
 | |
|      is stored in the first two bits.
 | |
| 
 | |
|   @note function made to write the result in backward direction with no
 | |
|   special sense or tricks both directions are equal in complicity
 | |
| 
 | |
|   @retval #    pointer on coded LSN
 | |
| */
 | |
| 
 | |
| static uchar *translog_put_LSN_diff(LSN base_lsn, LSN lsn, uchar *dst)
 | |
| {
 | |
|   uint64 diff;
 | |
|   DBUG_ENTER("translog_put_LSN_diff");
 | |
|   DBUG_PRINT("enter", ("Base: " LSN_FMT "  val: " LSN_FMT "  dst: %p",
 | |
|                        LSN_IN_PARTS(base_lsn), LSN_IN_PARTS(lsn),
 | |
|                        dst));
 | |
|   DBUG_ASSERT(base_lsn > lsn);
 | |
|   diff= base_lsn - lsn;
 | |
|   DBUG_PRINT("info", ("Diff: 0x%llx", (ulonglong) diff));
 | |
|   if (diff <= 0x3FFF)
 | |
|   {
 | |
|     dst-= 2;
 | |
|     /*
 | |
|       Note we store this high uchar first to ensure that first uchar has
 | |
|       0 in the 3 upper bits.
 | |
|     */
 | |
|     dst[0]= (uchar)(diff >> 8);
 | |
|     dst[1]= (uchar)(diff & 0xFF);
 | |
|   }
 | |
|   else if (diff <= 0x3FFFFFL)
 | |
|   {
 | |
|     dst-= 3;
 | |
|     dst[0]= (uchar)(0x40 | (diff >> 16));
 | |
|     int2store(dst + 1, diff & 0xFFFF);
 | |
|   }
 | |
|   else if (diff <= 0x3FFFFFFFL)
 | |
|   {
 | |
|     dst-= 4;
 | |
|     dst[0]= (uchar)(0x80 | (diff >> 24));
 | |
|     int3store(dst + 1, diff & 0xFFFFFFL);
 | |
|   }
 | |
|   else if (diff <= 0x3FFFFFFFFFLL)
 | |
| 
 | |
|   {
 | |
|     dst-= 5;
 | |
|     dst[0]= (uchar)(0xC0 | (diff >> 32));
 | |
|     int4store(dst + 1, diff & 0xFFFFFFFFL);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       It is full LSN after special 1 diff (which is impossible
 | |
|       in real life)
 | |
|     */
 | |
|     dst-= 2 + LSN_STORE_SIZE;
 | |
|     dst[0]= 0;
 | |
|     dst[1]= 1;
 | |
|     lsn_store(dst + 2, lsn);
 | |
|   }
 | |
|   DBUG_PRINT("info", ("new dst: %p", dst));
 | |
|   DBUG_RETURN(dst);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get LSN from LSN-difference (compressed LSN)
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_LSN_from_diff()
 | |
|     base_lsn             LSN from which we calculate difference
 | |
|     src                  pointer to coded lsn
 | |
|     dst                  pointer to buffer where to write 7byte LSN
 | |
| 
 | |
|   NOTE:
 | |
|     To store an LSN in a compact way we will use the following compression:
 | |
| 
 | |
|     If a log record has LSN1, and it contains the lSN2 as a back reference,
 | |
|     Instead of LSN2 we write LSN1-LSN2, encoded as:
 | |
| 
 | |
|      two bits     the number N (see below)
 | |
|      14 bits
 | |
|      N bytes
 | |
| 
 | |
|     That is, LSN is encoded in 2..5 bytes, and the number of bytes minus 2
 | |
|     is stored in the first two bits.
 | |
| 
 | |
|   RETURN
 | |
|     pointer to buffer after decoded LSN
 | |
| */
 | |
| 
 | |
| static uchar *translog_get_LSN_from_diff(LSN base_lsn, uchar *src, uchar *dst)
 | |
| {
 | |
|   LSN lsn;
 | |
|   uint32 diff;
 | |
|   uint32 first_byte;
 | |
|   uint32 file_no, rec_offset;
 | |
|   uint8 code;
 | |
|   DBUG_ENTER("translog_get_LSN_from_diff");
 | |
|   DBUG_PRINT("enter", ("Base: " LSN_FMT "  src:%p  dst %p",
 | |
|                        LSN_IN_PARTS(base_lsn), src, dst));
 | |
|   first_byte= *((uint8*) src);
 | |
|   code= first_byte >> 6; /* Length is in 2 most significant bits */
 | |
|   first_byte&= 0x3F;
 | |
|   src++;                                        /* Skip length + encode */
 | |
|   file_no= LSN_FILE_NO(base_lsn);               /* Assume relative */
 | |
|   DBUG_PRINT("info", ("code: %u  first byte: %lu",
 | |
|                       (uint) code, (ulong) first_byte));
 | |
|   switch (code) {
 | |
|   case 0:
 | |
|     if (first_byte == 0 && *((uint8*)src) == 1)
 | |
|     {
 | |
|       /*
 | |
|         It is full LSN after special 1 diff (which is impossible
 | |
|         in real life)
 | |
|       */
 | |
|       memcpy(dst, src + 1, LSN_STORE_SIZE);
 | |
|       DBUG_PRINT("info", ("Special case of full LSN, new src:%p",
 | |
|                           src + 1 + LSN_STORE_SIZE));
 | |
|       DBUG_RETURN(src + 1 + LSN_STORE_SIZE);
 | |
|     }
 | |
|     rec_offset= LSN_OFFSET(base_lsn) - ((first_byte << 8) | *((uint8*)src));
 | |
|     break;
 | |
|   case 1:
 | |
|     diff= uint2korr(src);
 | |
|     rec_offset= LSN_OFFSET(base_lsn) - ((first_byte << 16) | diff);
 | |
|     break;
 | |
|   case 2:
 | |
|     diff= uint3korr(src);
 | |
|     rec_offset= LSN_OFFSET(base_lsn) - ((first_byte << 24) | diff);
 | |
|     break;
 | |
|   case 3:
 | |
|   {
 | |
|     ulonglong base_offset= LSN_OFFSET(base_lsn);
 | |
|     diff= uint4korr(src);
 | |
|     if (diff > LSN_OFFSET(base_lsn))
 | |
|     {
 | |
|       /* take 1 from file offset */
 | |
|       first_byte++;
 | |
|       base_offset+= 0x100000000LL;
 | |
|     }
 | |
|     file_no= LSN_FILE_NO(base_lsn) - first_byte;
 | |
|     DBUG_ASSERT(base_offset - diff <= UINT_MAX);
 | |
|     rec_offset= (uint32)(base_offset - diff);
 | |
|     break;
 | |
|   }
 | |
|   default:
 | |
|     DBUG_ASSERT(0);
 | |
|     DBUG_RETURN(NULL);
 | |
|   }
 | |
|   lsn= MAKE_LSN(file_no, rec_offset);
 | |
|   src+= code + 1;
 | |
|   lsn_store(dst, lsn);
 | |
|   DBUG_PRINT("info", ("new src:%p", src));
 | |
|   DBUG_RETURN(src);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Encodes relative LSNs listed in the parameters.
 | |
| 
 | |
|   @param parts           Parts list with encoded LSN(s)
 | |
|   @param base_lsn        LSN which is base for encoding
 | |
|   @param lsns            number of LSN(s) to encode
 | |
|   @param compressed_LSNs buffer which can be used for storing compressed LSN(s)
 | |
| */
 | |
| 
 | |
| static void  translog_relative_LSN_encode(struct st_translog_parts *parts,
 | |
|                                           LSN base_lsn,
 | |
|                                           uint lsns, uchar *compressed_LSNs)
 | |
| {
 | |
|   LEX_CUSTRING *part;
 | |
|   uint lsns_len= lsns * LSN_STORE_SIZE;
 | |
|   uchar buffer_src[MAX_NUMBER_OF_LSNS_PER_RECORD * LSN_STORE_SIZE];
 | |
|   uchar *buffer= buffer_src;
 | |
|   const uchar *cbuffer;
 | |
| 
 | |
|   DBUG_ENTER("translog_relative_LSN_encode");
 | |
| 
 | |
|   DBUG_ASSERT(parts->current != 0);
 | |
|   part= parts->parts + parts->current;
 | |
| 
 | |
|   /* collect all LSN(s) in one chunk if it (they) is (are) divided */
 | |
|   if (part->length < lsns_len)
 | |
|   {
 | |
|     size_t copied= part->length;
 | |
|     LEX_CUSTRING *next_part;
 | |
|     DBUG_PRINT("info", ("Using buffer:%p", compressed_LSNs));
 | |
|     memcpy(buffer, part->str, part->length);
 | |
|     next_part= parts->parts + parts->current + 1;
 | |
|     do
 | |
|     {
 | |
|       DBUG_ASSERT(next_part < parts->parts + parts->elements);
 | |
|       if ((next_part->length + copied) < lsns_len)
 | |
|       {
 | |
|         memcpy(buffer + copied, next_part->str,
 | |
|                next_part->length);
 | |
|         copied+= next_part->length;
 | |
|         next_part->length= 0; next_part->str= 0;
 | |
|         /* delete_dynamic_element(&parts->parts, parts->current + 1); */
 | |
|         next_part++;
 | |
|         parts->current++;
 | |
|         part= parts->parts + parts->current;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         size_t len= lsns_len - copied;
 | |
|         memcpy(buffer + copied, next_part->str, len);
 | |
|         copied= lsns_len;
 | |
|         next_part->str+= len;
 | |
|         next_part->length-= len;
 | |
|       }
 | |
|     } while (copied < lsns_len);
 | |
|     cbuffer= buffer;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     cbuffer= part->str;
 | |
|     part->str+= lsns_len;
 | |
|     part->length-= lsns_len;
 | |
|     parts->current--;
 | |
|     part= parts->parts + parts->current;
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     /* Compress */
 | |
|     LSN ref;
 | |
|     int economy;
 | |
|     const uchar *src_ptr;
 | |
|     uchar *dst_ptr= compressed_LSNs + (MAX_NUMBER_OF_LSNS_PER_RECORD *
 | |
|                                       COMPRESSED_LSN_MAX_STORE_SIZE);
 | |
|     /*
 | |
|       We write the result in backward direction with no special sense or
 | |
|       tricks both directions are equal in complicity
 | |
|     */
 | |
|     for (src_ptr= cbuffer + lsns_len - LSN_STORE_SIZE;
 | |
|          src_ptr >= (const uchar*)cbuffer;
 | |
|          src_ptr-= LSN_STORE_SIZE)
 | |
|     {
 | |
|       ref= lsn_korr(src_ptr);
 | |
|       dst_ptr= translog_put_LSN_diff(base_lsn, ref, dst_ptr);
 | |
|     }
 | |
|     part->length= (size_t)((compressed_LSNs +
 | |
|                           (MAX_NUMBER_OF_LSNS_PER_RECORD *
 | |
|                            COMPRESSED_LSN_MAX_STORE_SIZE)) -
 | |
|                          dst_ptr);
 | |
|     economy= lsns_len - (uint)part->length;
 | |
|     parts->record_length-= economy;
 | |
|     DBUG_PRINT("info", ("new length of LSNs: %lu  economy: %d",
 | |
|                         (ulong)part->length, economy));
 | |
|     parts->total_record_length-= economy;
 | |
|     part->str= dst_ptr;
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Write multi-group variable-size record.
 | |
| 
 | |
|    @param  lsn             LSN of the record will be written here
 | |
|    @param  type            the log record type
 | |
|    @param  short_trid      Short transaction ID or 0 if it has no sense
 | |
|    @param  parts           Descriptor of record source parts
 | |
|    @param  buffer_to_flush Buffer which have to be flushed if it is not 0
 | |
|    @param  header_length   Header length calculated for 1 group
 | |
|    @param  buffer_rest     Beginning from which we plan to write in full pages
 | |
|    @param  trn             Transaction structure pointer for hooks by
 | |
|                            record log type, for short_id
 | |
|    @param  hook_arg        Argument which will be passed to pre-write and
 | |
|                            in-write hooks of this record.
 | |
| 
 | |
|    @note
 | |
|      We must have a translog_lock() when entering this function
 | |
| 
 | |
|      We must have buffer_to_flush locked (if not null)
 | |
|      buffer_to_flush should *NOT* be locked when calling this function.
 | |
|      (This is note is here as this is different from most other
 | |
|      translog_write...() functions which require the buffer to be locked)
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_write_variable_record_mgroup(LSN *lsn,
 | |
|                                       enum translog_record_type type,
 | |
|                                       MARIA_HA *tbl_info,
 | |
|                                       SHORT_TRANSACTION_ID short_trid,
 | |
|                                       struct st_translog_parts *parts,
 | |
|                                       struct st_translog_buffer
 | |
|                                       *buffer_to_flush,
 | |
|                                       uint16 header_length,
 | |
|                                       translog_size_t buffer_rest,
 | |
|                                       TRN *trn, void *hook_arg)
 | |
| {
 | |
|   TRANSLOG_ADDRESS horizon;
 | |
|   struct st_buffer_cursor cursor;
 | |
|   int rc= 0;
 | |
|   uint i, chunk2_page, full_pages;
 | |
|   uint curr_group= 0;
 | |
|   translog_size_t record_rest, first_page, chunk3_pages, chunk0_pages= 1;
 | |
|   translog_size_t done= 0;
 | |
|   struct st_translog_group_descriptor group;
 | |
|   DYNAMIC_ARRAY groups;
 | |
|   uint16 chunk3_size;
 | |
|   uint16 page_capacity= log_descriptor.page_capacity_chunk_2 + 1;
 | |
|   uint16 last_page_capacity;
 | |
|   my_bool new_page_before_chunk0= 1, first_chunk0= 1;
 | |
|   uchar chunk0_header[1 + 2 + 5 + 2 + 2], group_desc[7 + 1];
 | |
|   uchar chunk2_header[1];
 | |
|   uint header_fixed_part= header_length + 2;
 | |
|   uint groups_per_page= (page_capacity - header_fixed_part) / (7 + 1);
 | |
|   uint file_of_the_first_group;
 | |
|   int pages_to_skip;
 | |
|   struct st_translog_buffer *buffer_of_last_lsn;
 | |
|   my_bool external_buffer_to_flush= TRUE;
 | |
|   DBUG_ENTER("translog_write_variable_record_mgroup");
 | |
|   translog_lock_assert_owner();
 | |
| 
 | |
|   used_buffs_init(&cursor.buffs);
 | |
|   chunk2_header[0]= TRANSLOG_CHUNK_NOHDR;
 | |
| 
 | |
|   if (my_init_dynamic_array(PSI_INSTRUMENT_ME, &groups,
 | |
|                             sizeof(struct st_translog_group_descriptor),
 | |
|                             10, 10, MYF(0)))
 | |
|   {
 | |
|     translog_unlock();
 | |
|     if (buffer_to_flush != NULL)
 | |
|     {
 | |
|       translog_buffer_flush(buffer_to_flush);
 | |
|       translog_buffer_unlock(buffer_to_flush);
 | |
|     }
 | |
|     DBUG_PRINT("error", ("init array failed"));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   first_page= translog_get_current_page_rest();
 | |
|   record_rest= parts->record_length - (first_page - 1);
 | |
|   DBUG_PRINT("info", ("Record Rest: %lu", (ulong) record_rest));
 | |
| 
 | |
|   if (record_rest < buffer_rest)
 | |
|   {
 | |
|     /*
 | |
|       The record (group 1 type) is larger than the free space on the page
 | |
|       - we need to split it in two. But when we split it in two, the first
 | |
|       part is big enough to hold all the data of the record (because the
 | |
|       header of the first part of the split is smaller than the header of
 | |
|       the record as a whole when it takes only one chunk)
 | |
|     */
 | |
|     DBUG_PRINT("info", ("too many free space because changing header"));
 | |
|     buffer_rest-= log_descriptor.page_capacity_chunk_2;
 | |
|     DBUG_ASSERT(record_rest >= buffer_rest);
 | |
|   }
 | |
| 
 | |
|   file_of_the_first_group= LSN_FILE_NO(log_descriptor.horizon);
 | |
|   translog_mark_file_unfinished(file_of_the_first_group);
 | |
|   do
 | |
|   {
 | |
|     DBUG_ASSERT(cursor.buffs.unlck_ptr == cursor.buffs.wrt_ptr);
 | |
|     group.addr= horizon= log_descriptor.horizon;
 | |
|     cursor= log_descriptor.bc;
 | |
|     cursor.chaser= 1;
 | |
|     if ((full_pages= buffer_rest / log_descriptor.page_capacity_chunk_2) > 255)
 | |
|     {
 | |
|       /* sizeof(uint8) == 256 is max number of chunk in multi-chunks group */
 | |
|       full_pages= 255;
 | |
|       buffer_rest= full_pages * log_descriptor.page_capacity_chunk_2;
 | |
|     }
 | |
|     /*
 | |
|        group chunks =
 | |
|        full pages + first page (which actually can be full, too).
 | |
|        But here we assign number of chunks - 1
 | |
|     */
 | |
|     group.num= full_pages;
 | |
|     if (insert_dynamic(&groups, (uchar*) &group))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("insert into array failed"));
 | |
|       goto err_unlock;
 | |
|     }
 | |
| 
 | |
|     DBUG_PRINT("info", ("chunk: #%u  first_page: %u (%u)  "
 | |
|                         "full_pages: %lu (%lu)  "
 | |
|                         "Left %lu",
 | |
|                         groups.elements,
 | |
|                         first_page, first_page - 1,
 | |
|                         (ulong) full_pages,
 | |
|                         (ulong) (full_pages *
 | |
|                                  log_descriptor.page_capacity_chunk_2),
 | |
|                         (ulong)(parts->record_length - (first_page - 1 +
 | |
|                                                         buffer_rest) -
 | |
|                                 done)));
 | |
|     rc= translog_advance_pointer((int)full_pages, 0, &cursor.buffs);
 | |
| 
 | |
|     translog_unlock();
 | |
| 
 | |
|     if (buffer_to_flush != NULL)
 | |
|     {
 | |
|       if (!external_buffer_to_flush)
 | |
|         translog_buffer_decrease_writers(buffer_to_flush);
 | |
|       if (!rc)
 | |
|         rc= translog_buffer_flush(buffer_to_flush);
 | |
|       translog_buffer_unlock(buffer_to_flush);
 | |
|       buffer_to_flush= NULL;
 | |
|     }
 | |
|     external_buffer_to_flush= FALSE;
 | |
| 
 | |
|     if (rc)
 | |
|     {
 | |
|       DBUG_PRINT("error", ("flush of unlock buffer failed"));
 | |
|       //translog_advance_pointer decreased writers so it is OK
 | |
|       DBUG_ASSERT(cursor.buffs.unlck_ptr == cursor.buffs.wrt_ptr);
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     translog_write_data_on_page(&horizon, &cursor, 1, chunk2_header);
 | |
|     translog_write_parts_on_page(&horizon, &cursor, first_page - 1, parts);
 | |
|     DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT "  "
 | |
|                         "Left  %lu",
 | |
|                         LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                         LSN_IN_PARTS(horizon),
 | |
|                         (ulong) (parts->record_length - (first_page - 1) -
 | |
|                                  done)));
 | |
| 
 | |
|     for (i= 0; i < full_pages; i++)
 | |
|     {
 | |
|       if (translog_write_variable_record_chunk2_page(parts, &horizon, &cursor))
 | |
|         goto err;
 | |
| 
 | |
|       DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  "
 | |
|                           "local: " LSN_FMT "  "
 | |
|                           "Left: %lu",
 | |
|                           LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                           LSN_IN_PARTS(horizon),
 | |
|                           (ulong) (parts->record_length - (first_page - 1) -
 | |
|                                    i * log_descriptor.page_capacity_chunk_2 -
 | |
|                                    done)));
 | |
|     }
 | |
| 
 | |
|     done+= (first_page - 1 + buffer_rest);
 | |
| 
 | |
|     if (translog_chaser_page_next(&horizon, &cursor))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("flush of unlock buffer failed"));
 | |
|       goto err;
 | |
|     }
 | |
|     translog_buffer_lock(cursor.buffer);
 | |
|     translog_buffer_decrease_writers(cursor.buffer);
 | |
|     used_buffs_register_unlock(&cursor.buffs, cursor.buffer);
 | |
|     translog_buffer_unlock(cursor.buffer);
 | |
| 
 | |
|     translog_lock();
 | |
| 
 | |
|     /* Check that we have place for chunk type 2 */
 | |
|     first_page= translog_get_current_page_rest();
 | |
|     if (first_page <= 1)
 | |
|     {
 | |
|       if (translog_page_next(&log_descriptor.horizon, &log_descriptor.bc,
 | |
|                              &buffer_to_flush))
 | |
|         goto err_unlock;
 | |
|       first_page= translog_get_current_page_rest();
 | |
|     }
 | |
|     buffer_rest= translog_get_current_group_size();
 | |
| 
 | |
|     if (buffer_to_flush)
 | |
|       used_buffs_register_unlock(&cursor.buffs,
 | |
|                                 buffer_to_flush); // will be unlocked
 | |
| 
 | |
|   } while ((translog_size_t)(first_page + buffer_rest) <
 | |
|            (translog_size_t)(parts->record_length - done));
 | |
| 
 | |
|   group.addr= horizon= log_descriptor.horizon;
 | |
|   cursor= log_descriptor.bc;
 | |
|   cursor.chaser= 1;
 | |
|   group.num= 0;                       /* 0 because it does not matter */
 | |
|   if (insert_dynamic(&groups, (uchar*) &group))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("insert into array failed"));
 | |
|     goto err_unlock;
 | |
|   }
 | |
|   record_rest= parts->record_length - done;
 | |
|   DBUG_PRINT("info", ("Record rest: %lu", (ulong) record_rest));
 | |
|   if (first_page > record_rest + 1)
 | |
|   {
 | |
|     /*
 | |
|       We have not so much data to fill all first page
 | |
|       (no speaking about full pages)
 | |
|       so it will be:
 | |
|       <chunk0 <data>>
 | |
|       or
 | |
|       <chunk0>...<chunk0><chunk0 <data>>
 | |
|       or
 | |
|       <chunk3 <data>><chunk0>...<chunk0><chunk0 <possible data of 1 byte>>
 | |
|     */
 | |
|     chunk2_page= full_pages= 0;
 | |
|     last_page_capacity= first_page;
 | |
|     pages_to_skip= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       We will have:
 | |
|       <chunk2 <data>>...<chunk2 <data>><chunk0 <data>>
 | |
|       or
 | |
|       <chunk2 <data>>...<chunk2 <data>><chunk0>...<chunk0><chunk0 <data>>
 | |
|       or
 | |
|       <chunk3 <data>><chunk0>...<chunk0><chunk0 <possible data of 1 byte>>
 | |
|     */
 | |
|     chunk2_page= 1;
 | |
|     record_rest-= (first_page - 1);
 | |
|     pages_to_skip= full_pages=
 | |
|       record_rest / log_descriptor.page_capacity_chunk_2;
 | |
|     record_rest= (record_rest % log_descriptor.page_capacity_chunk_2);
 | |
|     last_page_capacity= page_capacity;
 | |
|   }
 | |
|   chunk3_size= 0;
 | |
|   chunk3_pages= 0;
 | |
|   if (last_page_capacity > record_rest + 1 && record_rest != 0)
 | |
|   {
 | |
|     if (last_page_capacity >
 | |
|         record_rest + header_fixed_part + groups.elements * (7 + 1))
 | |
|     {
 | |
|       /* 1 record of type 0 */
 | |
|       chunk3_pages= 0;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       pages_to_skip++;
 | |
|       chunk3_pages= 1;
 | |
|       if (record_rest + 2 == last_page_capacity)
 | |
|       {
 | |
|         chunk3_size= record_rest - 1;
 | |
|         record_rest= 1;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         chunk3_size= record_rest;
 | |
|         record_rest= 0;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   /*
 | |
|      A first non-full page will hold type 0 chunk only if it fit in it with
 | |
|      all its headers
 | |
|   */
 | |
|   while (page_capacity <
 | |
|          record_rest + header_fixed_part +
 | |
|          (groups.elements - groups_per_page * (chunk0_pages - 1)) * (7 + 1))
 | |
|     chunk0_pages++;
 | |
|   DBUG_PRINT("info", ("chunk0_pages: %u  groups %u  groups per full page: %u  "
 | |
|                       "Group on last page: %u",
 | |
|                       chunk0_pages, groups.elements,
 | |
|                       groups_per_page,
 | |
|                       (groups.elements -
 | |
|                        ((page_capacity - header_fixed_part) / (7 + 1)) *
 | |
|                        (chunk0_pages - 1))));
 | |
|   DBUG_PRINT("info", ("first_page: %u  chunk2: %u  full_pages: %u (%lu)  "
 | |
|                       "chunk3: %u (%u)  rest: %u",
 | |
|                       first_page,
 | |
|                       chunk2_page, full_pages,
 | |
|                       (ulong) full_pages *
 | |
|                       log_descriptor.page_capacity_chunk_2,
 | |
|                       chunk3_pages, (uint) chunk3_size, (uint) record_rest));
 | |
| 
 | |
|   DBUG_ASSERT(cursor.buffs.unlck_ptr == cursor.buffs.wrt_ptr);
 | |
|   rc= translog_advance_pointer(pages_to_skip + (int)(chunk0_pages - 1),
 | |
|                                record_rest + header_fixed_part +
 | |
|                                (groups.elements -
 | |
|                                 ((page_capacity -
 | |
|                                   header_fixed_part) / (7 + 1)) *
 | |
|                                 (chunk0_pages - 1)) * (7 + 1),
 | |
|                                 &cursor.buffs);
 | |
|   buffer_of_last_lsn= log_descriptor.bc.buffer;
 | |
|   translog_unlock();
 | |
| 
 | |
|   if (buffer_to_flush != NULL)
 | |
|   {
 | |
|     DBUG_ASSERT(!external_buffer_to_flush);
 | |
|     translog_buffer_decrease_writers(buffer_to_flush);
 | |
|     if (!rc)
 | |
|       rc= translog_buffer_flush(buffer_to_flush);
 | |
|     translog_buffer_unlock(buffer_to_flush);
 | |
|     buffer_to_flush= NULL;
 | |
|   }
 | |
|   if (rc)
 | |
|   {
 | |
|     DBUG_PRINT("error", ("flush of unlock buffer failed"));
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   if (rc)
 | |
|     goto err;
 | |
| 
 | |
|   if (chunk2_page)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("chunk 2 to finish first page"));
 | |
|     translog_write_data_on_page(&horizon, &cursor, 1, chunk2_header);
 | |
|     translog_write_parts_on_page(&horizon, &cursor, first_page - 1, parts);
 | |
|     DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT " "
 | |
|                         "Left: %lu",
 | |
|                         LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                         LSN_IN_PARTS(horizon),
 | |
|                         (ulong) (parts->record_length - (first_page - 1) -
 | |
|                                  done)));
 | |
|   }
 | |
|   else if (chunk3_pages)
 | |
|   {
 | |
|     uchar chunk3_header[3];
 | |
|     DBUG_PRINT("info", ("chunk 3"));
 | |
|     DBUG_ASSERT(full_pages == 0);
 | |
|     chunk3_pages= 0;
 | |
|     chunk3_header[0]= TRANSLOG_CHUNK_LNGTH;
 | |
|     int2store(chunk3_header + 1, chunk3_size);
 | |
|     translog_write_data_on_page(&horizon, &cursor, 3, chunk3_header);
 | |
|     translog_write_parts_on_page(&horizon, &cursor, chunk3_size, parts);
 | |
|     DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT " "
 | |
|                         "Left: %lu",
 | |
|                         LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                         LSN_IN_PARTS(horizon),
 | |
|                         (ulong) (parts->record_length - chunk3_size - done)));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     DBUG_PRINT("info", ("no new_page_before_chunk0"));
 | |
|     new_page_before_chunk0= 0;
 | |
|   }
 | |
| 
 | |
|   for (i= 0; i < full_pages; i++)
 | |
|   {
 | |
|     DBUG_ASSERT(chunk2_page != 0);
 | |
|     if (translog_write_variable_record_chunk2_page(parts, &horizon, &cursor))
 | |
|       goto err;
 | |
| 
 | |
|     DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT " "
 | |
|                         "Left: %lu",
 | |
|                         LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                         LSN_IN_PARTS(horizon),
 | |
|                         (ulong) (parts->record_length - (first_page - 1) -
 | |
|                                  i * log_descriptor.page_capacity_chunk_2 -
 | |
|                                  done)));
 | |
|   }
 | |
| 
 | |
|   if (chunk3_pages &&
 | |
|       translog_write_variable_record_chunk3_page(parts,
 | |
|                                                  chunk3_size,
 | |
|                                                  &horizon, &cursor))
 | |
|     goto err;
 | |
|   DBUG_PRINT("info", ("absolute horizon: " LSN_FMT "  local: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(log_descriptor.horizon),
 | |
|                       LSN_IN_PARTS(horizon)));
 | |
| 
 | |
|   *chunk0_header= (uchar) (type | TRANSLOG_CHUNK_LSN);
 | |
|   int2store(chunk0_header + 1, short_trid);
 | |
|   translog_write_variable_record_1group_code_len(chunk0_header + 3,
 | |
|                                                  parts->record_length,
 | |
|                                                  header_length);
 | |
|   do
 | |
|   {
 | |
|     int limit;
 | |
|     if (new_page_before_chunk0 &&
 | |
|         translog_chaser_page_next(&horizon, &cursor))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("flush of unlock buffer failed"));
 | |
|       goto err;
 | |
|     }
 | |
|     new_page_before_chunk0= 1;
 | |
| 
 | |
|     if (first_chunk0)
 | |
|     {
 | |
|       first_chunk0= 0;
 | |
| 
 | |
|       /*
 | |
|         We can drop "log_descriptor.is_everything_flushed" earlier when have
 | |
|         lock on loghandler and assign initial value of "horizon" variable or
 | |
|         before unlocking loghandler (because we will increase writers
 | |
|         counter on the buffer and every thread which wanted flush the buffer
 | |
|         will wait till we finish with it). But IMHO better here take short
 | |
|         lock and do not bother other threads with waiting.
 | |
|       */
 | |
|       translog_lock();
 | |
|       set_lsn(lsn, horizon);
 | |
|       buffer_of_last_lsn->last_lsn= *lsn;
 | |
|       DBUG_PRINT("info", ("last_lsn set to " LSN_FMT "  buffer: %p",
 | |
|                           LSN_IN_PARTS(buffer_of_last_lsn->last_lsn),
 | |
|                           buffer_of_last_lsn));
 | |
|       if (log_record_type_descriptor[type].inwrite_hook &&
 | |
|           (*log_record_type_descriptor[type].inwrite_hook) (type, trn,
 | |
|                                                             tbl_info,
 | |
|                                                             lsn, hook_arg))
 | |
|         goto err_unlock;
 | |
|       translog_unlock();
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|        A first non-full page will hold type 0 chunk only if it fit in it with
 | |
|        all its headers => the fist page is full or number of groups less then
 | |
|        possible number of full page.
 | |
|     */
 | |
|     limit= (groups_per_page < groups.elements - curr_group ?
 | |
|             groups_per_page : groups.elements - curr_group);
 | |
|     DBUG_PRINT("info", ("Groups: %u  curr: %u  limit: %u",
 | |
|                         (uint) groups.elements, (uint) curr_group,
 | |
|                         (uint) limit));
 | |
| 
 | |
|     if (chunk0_pages == 1)
 | |
|     {
 | |
|       DBUG_PRINT("info", ("chunk_len: 2 + %u * (7+1) + %u = %u",
 | |
|                           (uint) limit, (uint) record_rest,
 | |
|                           (uint) (2 + limit * (7 + 1) + record_rest)));
 | |
|       int2store(chunk0_header + header_length - 2,
 | |
|                 2 + limit * (7 + 1) + record_rest);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       DBUG_PRINT("info", ("chunk_len: 2 + %u * (7+1) = %u",
 | |
|                           (uint) limit, (uint) (2 + limit * (7 + 1))));
 | |
|       int2store(chunk0_header + header_length - 2, 2 + limit * (7 + 1));
 | |
|     }
 | |
|     int2store(chunk0_header + header_length, groups.elements - curr_group);
 | |
|     translog_write_data_on_page(&horizon, &cursor, header_fixed_part,
 | |
|                                 chunk0_header);
 | |
|     for (i= curr_group; i < limit + curr_group; i++)
 | |
|     {
 | |
|       struct st_translog_group_descriptor *grp_ptr;
 | |
|       grp_ptr= dynamic_element(&groups, i,
 | |
|                                struct st_translog_group_descriptor *);
 | |
|       lsn_store(group_desc, grp_ptr->addr);
 | |
|       group_desc[7]= grp_ptr->num;
 | |
|       translog_write_data_on_page(&horizon, &cursor, (7 + 1), group_desc);
 | |
|     }
 | |
| 
 | |
|     if (chunk0_pages == 1 && record_rest != 0)
 | |
|       translog_write_parts_on_page(&horizon, &cursor, record_rest, parts);
 | |
| 
 | |
|     chunk0_pages--;
 | |
|     curr_group+= limit;
 | |
|     /* put special type to indicate that it is not LSN chunk */
 | |
|     *chunk0_header= (uchar) (TRANSLOG_CHUNK_LSN | TRANSLOG_CHUNK_0_CONT);
 | |
|   } while (chunk0_pages != 0);
 | |
|   translog_buffer_lock(cursor.buffer);
 | |
|   translog_buffer_decrease_writers(cursor.buffer);
 | |
|   used_buffs_register_unlock(&cursor.buffs, cursor.buffer);
 | |
|   translog_buffer_unlock(cursor.buffer);
 | |
|   rc= 0;
 | |
|   DBUG_ASSERT(cursor.buffs.unlck_ptr == cursor.buffs.wrt_ptr);
 | |
| 
 | |
|   if (translog_set_lsn_for_files(file_of_the_first_group, LSN_FILE_NO(*lsn),
 | |
|                                  *lsn, FALSE))
 | |
|     goto err;
 | |
| 
 | |
|   translog_mark_file_finished(file_of_the_first_group);
 | |
| 
 | |
|   delete_dynamic(&groups);
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
| err_unlock:
 | |
| 
 | |
|   translog_unlock();
 | |
| 
 | |
| err:
 | |
| 
 | |
|   if (cursor.buffs.unlck_ptr != cursor.buffs.wrt_ptr)
 | |
|     used_buffs_urgent_unlock(&cursor.buffs);
 | |
| 
 | |
|   if (buffer_to_flush != NULL)
 | |
|   {
 | |
|     /* This is to prevent locking buffer forever in case of error */
 | |
|     if (!external_buffer_to_flush)
 | |
|       translog_buffer_decrease_writers(buffer_to_flush);
 | |
|     if (!rc)
 | |
|       rc= translog_buffer_flush(buffer_to_flush);
 | |
|     translog_buffer_unlock(buffer_to_flush);
 | |
|     buffer_to_flush= NULL;
 | |
|   }
 | |
| 
 | |
| 
 | |
|   translog_mark_file_finished(file_of_the_first_group);
 | |
| 
 | |
|   delete_dynamic(&groups);
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Write the variable length log record.
 | |
| 
 | |
|    @param  lsn             LSN of the record will be written here
 | |
|    @param  type            the log record type
 | |
|    @param  short_trid      Short transaction ID or 0 if it has no sense
 | |
|    @param  parts           Descriptor of record source parts
 | |
|    @param  trn             Transaction structure pointer for hooks by
 | |
|                            record log type, for short_id
 | |
|    @param  hook_arg        Argument which will be passed to pre-write and
 | |
|                            in-write hooks of this record.
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_write_variable_record(LSN *lsn,
 | |
|                                               enum translog_record_type type,
 | |
|                                               MARIA_HA *tbl_info,
 | |
|                                               SHORT_TRANSACTION_ID short_trid,
 | |
|                                               struct st_translog_parts *parts,
 | |
|                                               TRN *trn, void *hook_arg)
 | |
| {
 | |
|   struct st_translog_buffer *buffer_to_flush= NULL;
 | |
|   uint header_length1= 1 + 2 + 2 +
 | |
|     translog_variable_record_length_bytes(parts->record_length);
 | |
|   ulong buffer_rest;
 | |
|   uint page_rest;
 | |
|   /* Max number of such LSNs per record is 2 */
 | |
|   uchar compressed_LSNs[MAX_NUMBER_OF_LSNS_PER_RECORD *
 | |
|     COMPRESSED_LSN_MAX_STORE_SIZE];
 | |
|   my_bool res;
 | |
|   DBUG_ENTER("translog_write_variable_record");
 | |
| 
 | |
|   translog_lock();
 | |
|   DBUG_PRINT("info", ("horizon: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(log_descriptor.horizon)));
 | |
|   page_rest= TRANSLOG_PAGE_SIZE - log_descriptor.bc.current_page_fill;
 | |
|   DBUG_PRINT("info", ("header length: %u  page_rest: %u",
 | |
|                       header_length1, page_rest));
 | |
| 
 | |
|   /*
 | |
|     header and part which we should read have to fit in one chunk
 | |
|     TODO: allow to divide readable header
 | |
|   */
 | |
|   if (page_rest <
 | |
|       (header_length1 + log_record_type_descriptor[type].read_header_len))
 | |
|   {
 | |
|     DBUG_PRINT("info",
 | |
|                ("Next page, size: %u  header: %u + %u",
 | |
|                 log_descriptor.bc.current_page_fill,
 | |
|                 header_length1,
 | |
|                 log_record_type_descriptor[type].read_header_len));
 | |
|     translog_page_next(&log_descriptor.horizon, &log_descriptor.bc,
 | |
|                        &buffer_to_flush);
 | |
|     /* Chunk 2 header is 1 byte, so full page capacity will be one uchar more */
 | |
|     page_rest= log_descriptor.page_capacity_chunk_2 + 1;
 | |
|     DBUG_PRINT("info", ("page_rest: %u", page_rest));
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|      To minimize compressed size we will compress always relative to
 | |
|      very first chunk address (log_descriptor.horizon for now)
 | |
|   */
 | |
|   if (log_record_type_descriptor[type].compressed_LSN > 0)
 | |
|   {
 | |
|     translog_relative_LSN_encode(parts, log_descriptor.horizon,
 | |
|                                  log_record_type_descriptor[type].
 | |
|                                  compressed_LSN, compressed_LSNs);
 | |
|     /* recalculate header length after compression */
 | |
|     header_length1= 1 + 2 + 2 +
 | |
|       translog_variable_record_length_bytes(parts->record_length);
 | |
|     DBUG_PRINT("info", ("after compressing LSN(s) header length: %u  "
 | |
|                         "record length: %lu",
 | |
|                         header_length1, (ulong)parts->record_length));
 | |
|   }
 | |
| 
 | |
|   /* TODO: check space on current page for header + few bytes */
 | |
|   if (page_rest >= parts->record_length + header_length1)
 | |
|   {
 | |
|     /* following function makes translog_unlock(); */
 | |
|     res= translog_write_variable_record_1chunk(lsn, type, tbl_info,
 | |
|                                                short_trid,
 | |
|                                                parts, buffer_to_flush,
 | |
|                                                header_length1, trn, hook_arg);
 | |
|     DBUG_RETURN(res);
 | |
|   }
 | |
| 
 | |
|   buffer_rest= translog_get_current_group_size();
 | |
| 
 | |
|   if (buffer_rest >= parts->record_length + header_length1 - page_rest)
 | |
|   {
 | |
|     /* following function makes translog_unlock(); */
 | |
|     res= translog_write_variable_record_1group(lsn, type, tbl_info,
 | |
|                                                short_trid,
 | |
|                                                parts, buffer_to_flush,
 | |
|                                                header_length1, trn, hook_arg);
 | |
|     DBUG_RETURN(res);
 | |
|   }
 | |
|   /* following function makes translog_unlock(); */
 | |
|   res= translog_write_variable_record_mgroup(lsn, type, tbl_info,
 | |
|                                              short_trid,
 | |
|                                              parts, buffer_to_flush,
 | |
|                                              header_length1,
 | |
|                                              buffer_rest, trn, hook_arg);
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Write the fixed and pseudo-fixed log record.
 | |
| 
 | |
|    @param  lsn             LSN of the record will be written here
 | |
|    @param  type            the log record type
 | |
|    @param  short_trid      Short transaction ID or 0 if it has no sense
 | |
|    @param  parts           Descriptor of record source parts
 | |
|    @param  trn             Transaction structure pointer for hooks by
 | |
|                            record log type, for short_id
 | |
|    @param  hook_arg        Argument which will be passed to pre-write and
 | |
|                            in-write hooks of this record.
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_write_fixed_record(LSN *lsn,
 | |
|                                            enum translog_record_type type,
 | |
|                                            MARIA_HA *tbl_info,
 | |
|                                            SHORT_TRANSACTION_ID short_trid,
 | |
|                                            struct st_translog_parts *parts,
 | |
|                                            TRN *trn, void *hook_arg)
 | |
| {
 | |
|   struct st_translog_buffer *buffer_to_flush= NULL;
 | |
|   uchar chunk1_header[1 + 2];
 | |
|   /* Max number of such LSNs per record is 2 */
 | |
|   uchar compressed_LSNs[MAX_NUMBER_OF_LSNS_PER_RECORD *
 | |
|     COMPRESSED_LSN_MAX_STORE_SIZE];
 | |
|   LEX_CUSTRING *part;
 | |
|   int rc= 1;
 | |
|   DBUG_ENTER("translog_write_fixed_record");
 | |
|   DBUG_ASSERT((log_record_type_descriptor[type].rclass ==
 | |
|                LOGRECTYPE_FIXEDLENGTH &&
 | |
|                parts->record_length ==
 | |
|                log_record_type_descriptor[type].fixed_length) ||
 | |
|               (log_record_type_descriptor[type].rclass ==
 | |
|                LOGRECTYPE_PSEUDOFIXEDLENGTH &&
 | |
|                parts->record_length ==
 | |
|                log_record_type_descriptor[type].fixed_length));
 | |
| 
 | |
|   translog_lock();
 | |
|   DBUG_PRINT("info", ("horizon: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(log_descriptor.horizon)));
 | |
| 
 | |
|   DBUG_ASSERT(log_descriptor.bc.current_page_fill <= TRANSLOG_PAGE_SIZE);
 | |
|   DBUG_PRINT("info",
 | |
|              ("Page size: %u  record: %u  next cond: %d",
 | |
|               log_descriptor.bc.current_page_fill,
 | |
|               (parts->record_length +
 | |
|                log_record_type_descriptor[type].compressed_LSN * 2 + 3),
 | |
|               ((((uint) log_descriptor.bc.current_page_fill) +
 | |
|                 (parts->record_length +
 | |
|                  log_record_type_descriptor[type].compressed_LSN * 2 + 3)) >
 | |
|                TRANSLOG_PAGE_SIZE)));
 | |
|   /*
 | |
|     check that there is enough place on current page.
 | |
|     NOTE: compressing may increase page LSN size on two bytes for every LSN
 | |
|   */
 | |
|   if ((((uint) log_descriptor.bc.current_page_fill) +
 | |
|        (parts->record_length +
 | |
|         log_record_type_descriptor[type].compressed_LSN * 2 + 3)) >
 | |
|       TRANSLOG_PAGE_SIZE)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Next page"));
 | |
|     if (translog_page_next(&log_descriptor.horizon, &log_descriptor.bc,
 | |
|                            &buffer_to_flush))
 | |
|       goto err;                                 /* rc == 1 */
 | |
|     if (buffer_to_flush)
 | |
|       translog_buffer_lock_assert_owner(buffer_to_flush);
 | |
|   }
 | |
| 
 | |
|   set_lsn(lsn, log_descriptor.horizon);
 | |
|   if (translog_set_lsn_for_files(LSN_FILE_NO(*lsn), LSN_FILE_NO(*lsn),
 | |
|                              *lsn, TRUE) ||
 | |
|       (log_record_type_descriptor[type].inwrite_hook &&
 | |
|        (*log_record_type_descriptor[type].inwrite_hook)(type, trn, tbl_info,
 | |
|                                                         lsn, hook_arg)))
 | |
|     goto err;
 | |
| 
 | |
|   /* compress LSNs */
 | |
|   if (log_record_type_descriptor[type].rclass ==
 | |
|       LOGRECTYPE_PSEUDOFIXEDLENGTH)
 | |
|   {
 | |
|     DBUG_ASSERT(log_record_type_descriptor[type].compressed_LSN > 0);
 | |
|     translog_relative_LSN_encode(parts, *lsn,
 | |
|                                  log_record_type_descriptor[type].
 | |
|                                  compressed_LSN, compressed_LSNs);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Write the whole record at once (we know that there is enough place on
 | |
|     the destination page)
 | |
|   */
 | |
|   DBUG_ASSERT(parts->current != 0);       /* first part is left for header */
 | |
|   part= parts->parts + (--parts->current);
 | |
|   parts->total_record_length+= (translog_size_t) (part->length= 1 + 2);
 | |
|   part->str= chunk1_header;
 | |
|   *chunk1_header= (uchar) (type | TRANSLOG_CHUNK_FIXED);
 | |
|   int2store(chunk1_header + 1, short_trid);
 | |
| 
 | |
|   rc= translog_write_parts_on_page(&log_descriptor.horizon,
 | |
|                                    &log_descriptor.bc,
 | |
|                                    parts->total_record_length, parts);
 | |
| 
 | |
|   log_descriptor.bc.buffer->last_lsn= *lsn;
 | |
|   DBUG_PRINT("info", ("last_lsn set to " LSN_FMT "  buffer: %p",
 | |
|                       LSN_IN_PARTS(log_descriptor.bc.buffer->last_lsn),
 | |
|                       log_descriptor.bc.buffer));
 | |
| 
 | |
| err:
 | |
|   translog_unlock();
 | |
| 
 | |
|   /*
 | |
|     check if we switched buffer and need process it (current buffer is
 | |
|     unlocked already => we will not delay other threads
 | |
|   */
 | |
|   if (buffer_to_flush != NULL)
 | |
|   {
 | |
|     if (!rc)
 | |
|       rc= translog_buffer_flush(buffer_to_flush);
 | |
|     translog_buffer_unlock(buffer_to_flush);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Writes the log record
 | |
| 
 | |
|    If share has no 2-byte-id yet, gives an id to the share and logs
 | |
|    LOGREC_FILE_ID. If transaction has not logged LOGREC_LONG_TRANSACTION_ID
 | |
|    yet, logs it.
 | |
| 
 | |
|    @param  lsn             LSN of the record will be written here
 | |
|    @param  type            the log record type
 | |
|    @param  trn             Transaction structure pointer for hooks by
 | |
|                            record log type, for short_id
 | |
|    @param  tbl_info        MARIA_HA of table or NULL
 | |
|    @param  rec_len         record length or 0 (count it)
 | |
|    @param  part_no         number of parts or 0 (count it)
 | |
|    @param  parts_data      zero ended (in case of number of parts is 0)
 | |
|                            array of LEX_STRINGs (parts), first
 | |
|                            TRANSLOG_INTERNAL_PARTS positions in the log
 | |
|                            should be unused (need for loghandler)
 | |
|    @param  store_share_id  if tbl_info!=NULL then share's id will
 | |
|                            automatically be stored in the two first bytes
 | |
|                            pointed (so pointer is assumed to be !=NULL)
 | |
|    @param  hook_arg        argument which will be passed to pre-write and
 | |
|                            in-write hooks of this record.
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| */
 | |
| 
 | |
| my_bool translog_write_record(LSN *lsn,
 | |
|                               enum translog_record_type type,
 | |
|                               TRN *trn, MARIA_HA *tbl_info,
 | |
|                               translog_size_t rec_len,
 | |
|                               uint part_no,
 | |
|                               LEX_CUSTRING *parts_data,
 | |
|                               uchar *store_share_id,
 | |
|                               void *hook_arg)
 | |
| {
 | |
|   struct st_translog_parts parts;
 | |
|   LEX_CUSTRING *part;
 | |
|   int rc;
 | |
|   SHORT_TRANSACTION_ID short_trid= trn->short_id;
 | |
|   DBUG_ENTER("translog_write_record");
 | |
|   DBUG_PRINT("enter", ("type: %u (%s)  ShortTrID: %u  rec_len: %lu",
 | |
|                        (uint) type, log_record_type_descriptor[type].name,
 | |
|                        (uint) short_trid, (ulong) rec_len));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
|   DBUG_ASSERT(type != 0);
 | |
|   DBUG_SLOW_ASSERT((uint)type <= max_allowed_translog_type);
 | |
|   if (unlikely(translog_status != TRANSLOG_OK))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("Transaction log is write protected"));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   if (tbl_info && type != LOGREC_FILE_ID)
 | |
|   {
 | |
|     MARIA_SHARE *share= tbl_info->s;
 | |
|     DBUG_ASSERT(share->now_transactional);
 | |
|     if (unlikely(share->id == 0))
 | |
|     {
 | |
|       /*
 | |
|         First log write for this MARIA_SHARE; give it a short id.
 | |
|         When the lock manager is enabled and needs a short id, it should be
 | |
|         assigned in the lock manager (because row locks will be taken before
 | |
|         log records are written; for example SELECT FOR UPDATE takes locks but
 | |
|         writes no log record.
 | |
|       */
 | |
|       if (unlikely(translog_assign_id_to_share(tbl_info, trn)))
 | |
|         DBUG_RETURN(1);
 | |
|     }
 | |
|     fileid_store(store_share_id, share->id);
 | |
|   }
 | |
|   if (unlikely(!(trn->first_undo_lsn & TRANSACTION_LOGGED_LONG_ID)))
 | |
|   {
 | |
|     LSN dummy_lsn;
 | |
|     LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1];
 | |
|     uchar log_data[6];
 | |
|     DBUG_ASSERT(trn->undo_lsn == LSN_IMPOSSIBLE);
 | |
|     int6store(log_data, trn->trid);
 | |
|     log_array[TRANSLOG_INTERNAL_PARTS + 0].str=    log_data;
 | |
|     log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data);
 | |
|     trn->first_undo_lsn|= TRANSACTION_LOGGED_LONG_ID; /* no recursion */
 | |
|     if (unlikely(translog_write_record(&dummy_lsn, LOGREC_LONG_TRANSACTION_ID,
 | |
|                                        trn, NULL, sizeof(log_data),
 | |
|                                        sizeof(log_array)/sizeof(log_array[0]),
 | |
|                                        log_array, NULL, NULL)))
 | |
|       DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   parts.parts= parts_data;
 | |
| 
 | |
|   /* count parts if they are not counted by upper level */
 | |
|   if (part_no == 0)
 | |
|   {
 | |
|     for (part_no= TRANSLOG_INTERNAL_PARTS;
 | |
|          parts_data[part_no].length != 0;
 | |
|          part_no++);
 | |
|   }
 | |
|   parts.elements= part_no;
 | |
|   parts.current= TRANSLOG_INTERNAL_PARTS;
 | |
| 
 | |
|   /* clear TRANSLOG_INTERNAL_PARTS */
 | |
|   compile_time_assert(TRANSLOG_INTERNAL_PARTS != 0);
 | |
|   parts_data[0].str= 0;
 | |
|   parts_data[0].length= 0;
 | |
| 
 | |
|   /* count length of the record */
 | |
|   if (rec_len == 0)
 | |
|   {
 | |
|     for(part= parts_data + TRANSLOG_INTERNAL_PARTS;\
 | |
|         part < parts_data + part_no;
 | |
|         part++)
 | |
|     {
 | |
|       rec_len+= (translog_size_t) part->length;
 | |
|     }
 | |
|   }
 | |
|   parts.record_length= rec_len;
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
|   {
 | |
|     uint i;
 | |
|     size_t len= 0;
 | |
| #ifdef HAVE_valgrind
 | |
|     ha_checksum checksum= 0;
 | |
| #endif
 | |
|     for (i= TRANSLOG_INTERNAL_PARTS; i < part_no; i++)
 | |
|     {
 | |
| #ifdef HAVE_valgrind
 | |
|       /* Find unitialized bytes early */
 | |
|       checksum+= my_checksum(checksum, parts_data[i].str,
 | |
|                              parts_data[i].length);
 | |
| #endif
 | |
|       len+= parts_data[i].length;
 | |
|     }
 | |
|     DBUG_ASSERT(len == rec_len);
 | |
|   }
 | |
| #endif
 | |
|   /*
 | |
|     Start total_record_length from record_length then overhead will
 | |
|     be add
 | |
|   */
 | |
|   parts.total_record_length= parts.record_length;
 | |
|   DBUG_PRINT("info", ("record length: %lu", (ulong) parts.record_length));
 | |
| 
 | |
|   /* process this parts */
 | |
|   if (!(rc= (log_record_type_descriptor[type].prewrite_hook &&
 | |
|              (*log_record_type_descriptor[type].prewrite_hook)(type, trn,
 | |
|                                                                tbl_info,
 | |
|                                                                hook_arg))))
 | |
|   {
 | |
|     switch (log_record_type_descriptor[type].rclass) {
 | |
|     case LOGRECTYPE_VARIABLE_LENGTH:
 | |
|       rc= translog_write_variable_record(lsn, type, tbl_info,
 | |
|                                          short_trid, &parts, trn, hook_arg);
 | |
|       break;
 | |
|     case LOGRECTYPE_PSEUDOFIXEDLENGTH:
 | |
|     case LOGRECTYPE_FIXEDLENGTH:
 | |
|       rc= translog_write_fixed_record(lsn, type, tbl_info,
 | |
|                                       short_trid, &parts, trn, hook_arg);
 | |
|       break;
 | |
|     case LOGRECTYPE_NOT_ALLOWED:
 | |
|     default:
 | |
|       DBUG_ASSERT(0);
 | |
|       rc= 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("info", ("LSN: " LSN_FMT, LSN_IN_PARTS(*lsn)));
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Decode compressed (relative) LSN(s)
 | |
| 
 | |
|   SYNOPSIS
 | |
|    translog_relative_lsn_decode()
 | |
|    base_lsn              LSN for encoding
 | |
|    src                   Decode LSN(s) from here
 | |
|    dst                   Put decoded LSNs here
 | |
|    lsns                  number of LSN(s)
 | |
| 
 | |
|    RETURN
 | |
|      position in sources after decoded LSN(s)
 | |
| */
 | |
| 
 | |
| static uchar *translog_relative_LSN_decode(LSN base_lsn,
 | |
|                                           uchar *src, uchar *dst, uint lsns)
 | |
| {
 | |
|   uint i;
 | |
|   for (i= 0; i < lsns; i++, dst+= LSN_STORE_SIZE)
 | |
|   {
 | |
|     src= translog_get_LSN_from_diff(base_lsn, src, dst);
 | |
|   }
 | |
|   return src;
 | |
| }
 | |
| 
 | |
| /**
 | |
|    @brief Get header of fixed/pseudo length record and call hook for
 | |
|    it processing
 | |
| 
 | |
|    @param page            Pointer to the buffer with page where LSN chunk is
 | |
|                           placed
 | |
|    @param page_offset     Offset of the first chunk in the page
 | |
|    @param buff            Buffer to be filled with header data
 | |
| 
 | |
|    @return Length of header or operation status
 | |
|      @retval #  number of bytes in TRANSLOG_HEADER_BUFFER::header where
 | |
|                 stored decoded part of the header
 | |
| */
 | |
| 
 | |
| static int translog_fixed_length_header(uchar *page,
 | |
|                                         translog_size_t page_offset,
 | |
|                                         TRANSLOG_HEADER_BUFFER *buff)
 | |
| {
 | |
|   struct st_log_record_type_descriptor *desc=
 | |
|     log_record_type_descriptor + buff->type;
 | |
|   uchar *src= page + page_offset + 3;
 | |
|   uchar *dst= buff->header;
 | |
|   uchar *start= src;
 | |
|   int lsns= desc->compressed_LSN;
 | |
|   uint length= desc->fixed_length;
 | |
|   DBUG_ENTER("translog_fixed_length_header");
 | |
| 
 | |
|   buff->record_length= length;
 | |
| 
 | |
|   if (desc->rclass == LOGRECTYPE_PSEUDOFIXEDLENGTH)
 | |
|   {
 | |
|     DBUG_ASSERT(lsns > 0);
 | |
|     src= translog_relative_LSN_decode(buff->lsn, src, dst, lsns);
 | |
|     lsns*= LSN_STORE_SIZE;
 | |
|     dst+= lsns;
 | |
|     length-= lsns;
 | |
|     buff->compressed_LSN_economy= (lsns - (int) (src - start));
 | |
|   }
 | |
|   else
 | |
|     buff->compressed_LSN_economy= 0;
 | |
| 
 | |
|   memcpy(dst, src, length);
 | |
|   buff->non_header_data_start_offset= (uint16) (page_offset +
 | |
|                                                 ((src + length) -
 | |
|                                                  (page + page_offset)));
 | |
|   buff->non_header_data_len= 0;
 | |
|   DBUG_RETURN(buff->record_length);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Free resources used by TRANSLOG_HEADER_BUFFER
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_free_record_header();
 | |
| */
 | |
| 
 | |
| void translog_free_record_header(TRANSLOG_HEADER_BUFFER *buff)
 | |
| {
 | |
|   DBUG_ENTER("translog_free_record_header");
 | |
|   if (buff->groups_no != 0)
 | |
|   {
 | |
|     my_free(buff->groups);
 | |
|     buff->groups_no= 0;
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Returns the current horizon at the end of the current log
 | |
| 
 | |
|    @return Horizon
 | |
|    @retval LSN_ERROR     error
 | |
|    @retvar #             Horizon
 | |
| */
 | |
| 
 | |
| TRANSLOG_ADDRESS translog_get_horizon()
 | |
| {
 | |
|   TRANSLOG_ADDRESS res;
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
|   translog_lock();
 | |
|   res= log_descriptor.horizon;
 | |
|   translog_unlock();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Returns the current horizon at the end of the current log, caller is
 | |
|    assumed to already hold the lock
 | |
| 
 | |
|    @return Horizon
 | |
|    @retval LSN_ERROR     error
 | |
|    @retvar #             Horizon
 | |
| */
 | |
| 
 | |
| TRANSLOG_ADDRESS translog_get_horizon_no_lock()
 | |
| {
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
|   translog_lock_assert_owner();
 | |
|   return log_descriptor.horizon;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Set last page in the scanner data structure
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_scanner_set_last_page()
 | |
|     scanner              Information about current chunk during scanning
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_scanner_set_last_page(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   my_bool page_ok;
 | |
|   if (LSN_FILE_NO(scanner->page_addr) == LSN_FILE_NO(scanner->horizon))
 | |
|   {
 | |
|     /* It is last file => we can easy find last page address by horizon */
 | |
|     uint pagegrest= LSN_OFFSET(scanner->horizon) % TRANSLOG_PAGE_SIZE;
 | |
|     scanner->last_file_page= (scanner->horizon -
 | |
|                               (pagegrest ? pagegrest : TRANSLOG_PAGE_SIZE));
 | |
|     return (0);
 | |
|   }
 | |
|   scanner->last_file_page= scanner->page_addr;
 | |
|   return (translog_get_last_page_addr(&scanner->last_file_page, &page_ok, 0));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Get page from page cache according to requested method
 | |
| 
 | |
|   @param scanner         The scanner data
 | |
| 
 | |
|   @return operation status
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_scanner_get_page(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   TRANSLOG_VALIDATOR_DATA data;
 | |
|   DBUG_ENTER("translog_scanner_get_page");
 | |
|   data.addr= &scanner->page_addr;
 | |
|   data.was_recovered= 0;
 | |
|   DBUG_RETURN((scanner->page=
 | |
|                translog_get_page(&data, scanner->buffer,
 | |
|                                  (scanner->use_direct_link ?
 | |
|                                   &scanner->direct_link :
 | |
|                                   NULL))) ==
 | |
|                NULL);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Initialize reader scanner.
 | |
| 
 | |
|   @param lsn             LSN with which it have to be inited
 | |
|   @param fixed_horizon   true if it is OK do not read records which was written
 | |
|                          after scanning beginning
 | |
|   @param scanner         scanner which have to be inited
 | |
|   @param use_direct      prefer using direct lings from page handler
 | |
|                          where it is possible.
 | |
| 
 | |
|   @note If direct link was used translog_destroy_scanner should be
 | |
|         called after it using
 | |
| 
 | |
|   @return status of the operation
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| my_bool translog_scanner_init(LSN lsn,
 | |
|                               my_bool fixed_horizon,
 | |
|                               TRANSLOG_SCANNER_DATA *scanner,
 | |
|                               my_bool use_direct)
 | |
| {
 | |
|   DBUG_ENTER("translog_scanner_init");
 | |
|   DBUG_PRINT("enter", ("Scanner: %p  LSN: " LSN_FMT,
 | |
|                        scanner, LSN_IN_PARTS(lsn)));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   scanner->page_offset= LSN_OFFSET(lsn) % TRANSLOG_PAGE_SIZE;
 | |
| 
 | |
|   scanner->fixed_horizon= fixed_horizon;
 | |
|   scanner->use_direct_link= use_direct;
 | |
|   scanner->direct_link= NULL;
 | |
| 
 | |
|   scanner->horizon= translog_get_horizon();
 | |
|   DBUG_PRINT("info", ("horizon: " LSN_FMT, LSN_IN_PARTS(scanner->horizon)));
 | |
| 
 | |
|   /* lsn < horizon */
 | |
|   DBUG_ASSERT(lsn <= scanner->horizon);
 | |
| 
 | |
|   scanner->page_addr= lsn;
 | |
|   scanner->page_addr-= scanner->page_offset; /*decrease offset */
 | |
| 
 | |
|   if (translog_scanner_set_last_page(scanner))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   if (translog_scanner_get_page(scanner))
 | |
|     DBUG_RETURN(1);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Destroy scanner object;
 | |
| 
 | |
|   @param scanner         The scanner object to destroy
 | |
| */
 | |
| 
 | |
| void translog_destroy_scanner(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   DBUG_ENTER("translog_destroy_scanner");
 | |
|   DBUG_PRINT("enter", ("Scanner: %p", scanner));
 | |
|   translog_free_link(scanner->direct_link);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Checks End of the Log
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_scanner_eol()
 | |
|     scanner              Information about current chunk during scanning
 | |
| 
 | |
|   RETURN
 | |
|     1  End of the Log
 | |
|     0  OK
 | |
| */
 | |
| 
 | |
| static my_bool translog_scanner_eol(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   DBUG_ENTER("translog_scanner_eol");
 | |
|   DBUG_PRINT("enter",
 | |
|              ("Horizon: " LSN_FMT "  Current: (%u, 0x%x+0x%x=0x%x)",
 | |
|               LSN_IN_PARTS(scanner->horizon),
 | |
|               LSN_IN_PARTS(scanner->page_addr),
 | |
|               (uint) scanner->page_offset,
 | |
|               (uint) (LSN_OFFSET(scanner->page_addr) + scanner->page_offset)));
 | |
|   if (scanner->horizon > (scanner->page_addr +
 | |
|                           scanner->page_offset))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Horizon is not reached"));
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
|   if (scanner->fixed_horizon)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Horizon is fixed and reached"));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   scanner->horizon= translog_get_horizon();
 | |
|   DBUG_PRINT("info",
 | |
|              ("Horizon is re-read, EOL: %d",
 | |
|               scanner->horizon <= (scanner->page_addr +
 | |
|                                    scanner->page_offset)));
 | |
|   DBUG_RETURN(scanner->horizon <= (scanner->page_addr +
 | |
|                                    scanner->page_offset));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Cheks End of the Page
 | |
| 
 | |
|   @param scanner         Information about current chunk during scanning
 | |
| 
 | |
|   @retval 1  End of the Page
 | |
|   @retval 0  OK
 | |
| */
 | |
| 
 | |
| static my_bool translog_scanner_eop(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   DBUG_ENTER("translog_scanner_eop");
 | |
|   DBUG_RETURN(scanner->page_offset >= TRANSLOG_PAGE_SIZE ||
 | |
|               scanner->page[scanner->page_offset] == TRANSLOG_FILLER);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Checks End of the File (i.e. we are scanning last page, which do not
 | |
|     mean end of this page)
 | |
| 
 | |
|   @param scanner         Information about current chunk during scanning
 | |
| 
 | |
|   @retval 1 End of the File
 | |
|   @retval 0 OK
 | |
| */
 | |
| 
 | |
| static my_bool translog_scanner_eof(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   DBUG_ENTER("translog_scanner_eof");
 | |
|   DBUG_ASSERT(LSN_FILE_NO(scanner->page_addr) ==
 | |
|               LSN_FILE_NO(scanner->last_file_page));
 | |
|   DBUG_PRINT("enter", ("curr Page: 0x%lx  last page: 0x%lx  "
 | |
|                        "normal EOF: %d",
 | |
|                        (ulong) LSN_OFFSET(scanner->page_addr),
 | |
|                        (ulong) LSN_OFFSET(scanner->last_file_page),
 | |
|                        LSN_OFFSET(scanner->page_addr) ==
 | |
|                        LSN_OFFSET(scanner->last_file_page)));
 | |
|   /*
 | |
|      TODO: detect damaged file EOF,
 | |
|      TODO: issue warning if damaged file EOF detected
 | |
|   */
 | |
|   DBUG_RETURN(scanner->page_addr ==
 | |
|               scanner->last_file_page);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Move scanner to the next chunk
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_get_next_chunk()
 | |
|     scanner              Information about current chunk during scanning
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool
 | |
| translog_get_next_chunk(TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   uint16 len;
 | |
|   DBUG_ENTER("translog_get_next_chunk");
 | |
| 
 | |
|   if (translog_scanner_eop(scanner))
 | |
|     len= TRANSLOG_PAGE_SIZE - scanner->page_offset;
 | |
|   else if ((len= translog_get_total_chunk_length(scanner->page,
 | |
|                                                  scanner->page_offset)) == 0)
 | |
|     DBUG_RETURN(1);
 | |
|   scanner->page_offset+= len;
 | |
| 
 | |
|   if (translog_scanner_eol(scanner))
 | |
|   {
 | |
|     scanner->page= END_OF_LOG;
 | |
|     scanner->page_offset= 0;
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
|   if (translog_scanner_eop(scanner))
 | |
|   {
 | |
|     /* before reading next page we should unpin current one if it was pinned */
 | |
|     translog_free_link(scanner->direct_link);
 | |
|     if (translog_scanner_eof(scanner))
 | |
|     {
 | |
|       DBUG_PRINT("info", ("horizon: " LSN_FMT "  pageaddr: " LSN_FMT,
 | |
|                           LSN_IN_PARTS(scanner->horizon),
 | |
|                           LSN_IN_PARTS(scanner->page_addr)));
 | |
|       /* if it is log end it have to be caught before */
 | |
|       DBUG_ASSERT(LSN_FILE_NO(scanner->horizon) >
 | |
|                   LSN_FILE_NO(scanner->page_addr));
 | |
|       scanner->page_addr+= LSN_ONE_FILE;
 | |
|       scanner->page_addr= LSN_REPLACE_OFFSET(scanner->page_addr,
 | |
|                                              TRANSLOG_PAGE_SIZE);
 | |
|       if (translog_scanner_set_last_page(scanner))
 | |
|         DBUG_RETURN(1);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       scanner->page_addr+= TRANSLOG_PAGE_SIZE; /* offset increased */
 | |
|     }
 | |
| 
 | |
|     if (translog_scanner_get_page(scanner))
 | |
|       DBUG_RETURN(1);
 | |
| 
 | |
|     scanner->page_offset= translog_get_first_chunk_offset(scanner->page);
 | |
|     if (translog_scanner_eol(scanner))
 | |
|     {
 | |
|       scanner->page= END_OF_LOG;
 | |
|       scanner->page_offset= 0;
 | |
|       DBUG_RETURN(0);
 | |
|     }
 | |
|     DBUG_ASSERT(scanner->page[scanner->page_offset] != TRANSLOG_FILLER);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Get header of variable length record and call hook for it processing
 | |
| 
 | |
|    @param page            Pointer to the buffer with page where LSN chunk is
 | |
|                           placed
 | |
|    @param page_offset     Offset of the first chunk in the page
 | |
|    @param buff            Buffer to be filled with header data
 | |
|    @param scanner         If present should be moved to the header page if
 | |
|                           it differ from LSN page
 | |
| 
 | |
|    @return                Length of header or operation status
 | |
|      @retval RECHEADER_READ_ERROR  error
 | |
|      @retval RECHEADER_READ_EOF    End of the log reached during the read
 | |
|      @retval #                     number of bytes in
 | |
|                                    TRANSLOG_HEADER_BUFFER::header where
 | |
|                                    stored decoded part of the header
 | |
| */
 | |
| 
 | |
| static int
 | |
| translog_variable_length_header(uchar *page, translog_size_t page_offset,
 | |
|                                 TRANSLOG_HEADER_BUFFER *buff,
 | |
|                                 TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   struct st_log_record_type_descriptor *desc= (log_record_type_descriptor +
 | |
|                                                buff->type);
 | |
|   uchar *src= page + page_offset + 1 + 2;
 | |
|   uchar *dst= buff->header;
 | |
|   LSN base_lsn;
 | |
|   uint lsns= desc->compressed_LSN;
 | |
|   uint16 chunk_len;
 | |
|   uint16 length= desc->read_header_len;
 | |
|   uint16 buffer_length= length;
 | |
|   uint16 body_len;
 | |
|   int rc;
 | |
|   TRANSLOG_SCANNER_DATA internal_scanner;
 | |
|   DBUG_ENTER("translog_variable_length_header");
 | |
| 
 | |
|   buff->record_length= translog_variable_record_1group_decode_len(&src);
 | |
|   chunk_len= uint2korr(src);
 | |
|   DBUG_PRINT("info", ("rec len: %lu  chunk len: %u  length: %u  bufflen: %u",
 | |
|                       (ulong) buff->record_length, (uint) chunk_len,
 | |
|                       (uint) length, (uint) buffer_length));
 | |
|   if (chunk_len == 0)
 | |
|   {
 | |
|     uint16 page_rest;
 | |
|     DBUG_PRINT("info", ("1 group"));
 | |
|     src+= 2;
 | |
|     page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page));
 | |
| 
 | |
|     base_lsn= buff->lsn;
 | |
|     body_len= MY_MIN(page_rest, buff->record_length);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     uint grp_no, curr;
 | |
|     uint header_to_skip;
 | |
|     uint16 page_rest;
 | |
| 
 | |
|     DBUG_PRINT("info", ("multi-group"));
 | |
|     grp_no= buff->groups_no= uint2korr(src + 2);
 | |
|     if (!(buff->groups=
 | |
|           (TRANSLOG_GROUP*) my_malloc(PSI_INSTRUMENT_ME, sizeof(TRANSLOG_GROUP) * grp_no,
 | |
|                                       MYF(0))))
 | |
|       DBUG_RETURN(RECHEADER_READ_ERROR);
 | |
|     DBUG_PRINT("info", ("Groups: %u", (uint) grp_no));
 | |
|     src+= (2 + 2);
 | |
|     page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page));
 | |
|     curr= 0;
 | |
|     header_to_skip= (uint) (src - (page + page_offset));
 | |
|     buff->chunk0_pages= 0;
 | |
| 
 | |
|     for (;;)
 | |
|     {
 | |
|       uint i, read_length= grp_no;
 | |
| 
 | |
|       buff->chunk0_pages++;
 | |
|       if (page_rest < grp_no * (7 + 1))
 | |
|         read_length= page_rest / (7 + 1);
 | |
|       DBUG_PRINT("info", ("Read chunk0 page#%u  read: %u  left: %u  "
 | |
|                           "start from: %u",
 | |
|                           buff->chunk0_pages, read_length, grp_no, curr));
 | |
|       for (i= 0; i < read_length; i++, curr++)
 | |
|       {
 | |
|         DBUG_ASSERT(curr < buff->groups_no);
 | |
|         buff->groups[curr].addr= lsn_korr(src + i * (7 + 1));
 | |
|         buff->groups[curr].num= src[i * (7 + 1) + 7];
 | |
|         DBUG_PRINT("info", ("group #%u " LSN_FMT "  chunks: %u",
 | |
|                             curr,
 | |
|                             LSN_IN_PARTS(buff->groups[curr].addr),
 | |
|                             (uint) buff->groups[curr].num));
 | |
|       }
 | |
|       grp_no-= read_length;
 | |
|       if (grp_no == 0)
 | |
|       {
 | |
|         if (scanner)
 | |
|         {
 | |
|           buff->chunk0_data_addr= scanner->page_addr;
 | |
|           /* offset increased */
 | |
|           buff->chunk0_data_addr+= (page_offset + header_to_skip +
 | |
|                                     read_length * (7 + 1));
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           buff->chunk0_data_addr= buff->lsn;
 | |
|           /* offset increased */
 | |
|           buff->chunk0_data_addr+= (header_to_skip + read_length * (7 + 1));
 | |
|         }
 | |
|         buff->chunk0_data_len= chunk_len - 2 - read_length * (7 + 1);
 | |
|         DBUG_PRINT("info", ("Data address: " LSN_FMT "  len: %u",
 | |
|                             LSN_IN_PARTS(buff->chunk0_data_addr),
 | |
|                             buff->chunk0_data_len));
 | |
|         break;
 | |
|       }
 | |
|       if (scanner == NULL)
 | |
|       {
 | |
|         DBUG_PRINT("info", ("use internal scanner for header reading"));
 | |
|         scanner= &internal_scanner;
 | |
|         if (translog_scanner_init(buff->lsn, 1, scanner, 0))
 | |
|         {
 | |
|           rc= RECHEADER_READ_ERROR;
 | |
|           goto exit_and_free;
 | |
|         }
 | |
|       }
 | |
|       if (translog_get_next_chunk(scanner))
 | |
|       {
 | |
|         if (scanner == &internal_scanner)
 | |
|           translog_destroy_scanner(scanner);
 | |
|         rc= RECHEADER_READ_ERROR;
 | |
|         goto exit_and_free;
 | |
|       }
 | |
|       if (scanner->page == END_OF_LOG)
 | |
|       {
 | |
|         if (scanner == &internal_scanner)
 | |
|           translog_destroy_scanner(scanner);
 | |
|         rc= RECHEADER_READ_EOF;
 | |
|         goto exit_and_free;
 | |
|       }
 | |
|       page= scanner->page;
 | |
|       page_offset= scanner->page_offset;
 | |
|       src= page + page_offset + header_to_skip;
 | |
|       chunk_len= uint2korr(src - 2 - 2);
 | |
|       DBUG_PRINT("info", ("Chunk len: %u", (uint) chunk_len));
 | |
|       page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page));
 | |
|     }
 | |
| 
 | |
|     if (scanner == NULL)
 | |
|     {
 | |
|       DBUG_PRINT("info", ("use internal scanner"));
 | |
|       scanner= &internal_scanner;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       translog_destroy_scanner(scanner);
 | |
|     }
 | |
|     base_lsn= buff->groups[0].addr;
 | |
|     translog_scanner_init(base_lsn, 1, scanner, scanner == &internal_scanner);
 | |
|     /* first group chunk is always chunk type 2 */
 | |
|     page= scanner->page;
 | |
|     page_offset= scanner->page_offset;
 | |
|     src= page + page_offset + 1;
 | |
|     page_rest= (uint16) (TRANSLOG_PAGE_SIZE - (src - page));
 | |
|     body_len= page_rest;
 | |
|     if (scanner == &internal_scanner)
 | |
|       translog_destroy_scanner(scanner);
 | |
|   }
 | |
|   if (lsns)
 | |
|   {
 | |
|     uchar *start= src;
 | |
|     src= translog_relative_LSN_decode(base_lsn, src, dst, lsns);
 | |
|     lsns*= LSN_STORE_SIZE;
 | |
|     dst+= lsns;
 | |
|     length-= lsns;
 | |
|     buff->record_length+= (buff->compressed_LSN_economy=
 | |
|                            (int) (lsns - (src - start)));
 | |
|     DBUG_PRINT("info", ("lsns: %u  length: %u  economy: %d  new length: %lu",
 | |
|                         lsns / LSN_STORE_SIZE, (uint) length,
 | |
|                         (int) buff->compressed_LSN_economy,
 | |
|                         (ulong) buff->record_length));
 | |
|     body_len-= (uint16) (src - start);
 | |
|   }
 | |
|   else
 | |
|     buff->compressed_LSN_economy= 0;
 | |
| 
 | |
|   DBUG_ASSERT(body_len >= length);
 | |
|   body_len-= length;
 | |
|   memcpy(dst, src, length);
 | |
|   buff->non_header_data_start_offset= (uint16) (src + length - page);
 | |
|   buff->non_header_data_len= body_len;
 | |
|   DBUG_PRINT("info", ("non_header_data_start_offset: %u  len: %u  buffer: %u",
 | |
|                       buff->non_header_data_start_offset,
 | |
|                       buff->non_header_data_len, buffer_length));
 | |
|   DBUG_RETURN(buffer_length);
 | |
| 
 | |
| exit_and_free:
 | |
|   my_free(buff->groups);
 | |
|   buff->groups_no= 0; /* prevent try to use of buff->groups */
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Read record header from the given buffer
 | |
| 
 | |
|    @param page            page content buffer
 | |
|    @param page_offset     offset of the chunk in the page
 | |
|    @param buff            destination buffer
 | |
|    @param scanner         If this is set the scanner will be moved to the
 | |
|                           record header page (differ from LSN page in case of
 | |
|                           multi-group records)
 | |
| 
 | |
|    @return Length of header or operation status
 | |
|      @retval RECHEADER_READ_ERROR  error
 | |
|      @retval #                     number of bytes in
 | |
|                                    TRANSLOG_HEADER_BUFFER::header where
 | |
|                                    stored decoded part of the header
 | |
| */
 | |
| 
 | |
| int translog_read_record_header_from_buffer(uchar *page,
 | |
|                                             uint16 page_offset,
 | |
|                                             TRANSLOG_HEADER_BUFFER *buff,
 | |
|                                             TRANSLOG_SCANNER_DATA *scanner)
 | |
| {
 | |
|   translog_size_t res;
 | |
|   DBUG_ENTER("translog_read_record_header_from_buffer");
 | |
|   DBUG_PRINT("info", ("page byte: 0x%x  offset: %u",
 | |
|                       (uint) page[page_offset], (uint) page_offset));
 | |
|   DBUG_ASSERT(translog_is_LSN_chunk(page[page_offset]));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
|   buff->type= (page[page_offset] & TRANSLOG_REC_TYPE);
 | |
|   buff->short_trid= uint2korr(page + page_offset + 1);
 | |
|   DBUG_PRINT("info", ("Type %u, Short TrID %u, LSN " LSN_FMT,
 | |
|                       (uint) buff->type, (uint)buff->short_trid,
 | |
|                       LSN_IN_PARTS(buff->lsn)));
 | |
|   /* Read required bytes from the header and call hook */
 | |
|   switch (log_record_type_descriptor[buff->type].rclass) {
 | |
|   case LOGRECTYPE_VARIABLE_LENGTH:
 | |
|     res= translog_variable_length_header(page, page_offset, buff,
 | |
|                                          scanner);
 | |
|     break;
 | |
|   case LOGRECTYPE_PSEUDOFIXEDLENGTH:
 | |
|   case LOGRECTYPE_FIXEDLENGTH:
 | |
|     res= translog_fixed_length_header(page, page_offset, buff);
 | |
|     break;
 | |
|   default:
 | |
|     DBUG_ASSERT(0); /* we read some junk (got no LSN) */
 | |
|     res= RECHEADER_READ_ERROR;
 | |
|   }
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Read record header and some fixed part of a record (the part depend
 | |
|    on record type).
 | |
| 
 | |
|    @param lsn             log record serial number (address of the record)
 | |
|    @param buff            log record header buffer
 | |
| 
 | |
|    @note Some type of record can be read completely by this call
 | |
|    @note "Decoded" header stored in TRANSLOG_HEADER_BUFFER::header (relative
 | |
|    LSN can be translated to absolute one), some fields can be added (like
 | |
|    actual header length in the record if the header has variable length)
 | |
| 
 | |
|    @return Length of header or operation status
 | |
|      @retval RECHEADER_READ_ERROR  error
 | |
|      @retval #                     number of bytes in
 | |
|                                    TRANSLOG_HEADER_BUFFER::header where
 | |
|                                    stored decoded part of the header
 | |
| */
 | |
| 
 | |
| int translog_read_record_header(LSN lsn, TRANSLOG_HEADER_BUFFER *buff)
 | |
| {
 | |
|   TRANSLOG_PAGE_SIZE_BUFF psize_buff;
 | |
|   uchar *page;
 | |
|   translog_size_t res, page_offset= LSN_OFFSET(lsn) % TRANSLOG_PAGE_SIZE;
 | |
|   PAGECACHE_BLOCK_LINK *direct_link;
 | |
|   TRANSLOG_ADDRESS addr;
 | |
|   TRANSLOG_VALIDATOR_DATA data;
 | |
|   DBUG_ENTER("translog_read_record_header");
 | |
|   DBUG_PRINT("enter", ("LSN: " LSN_FMT, LSN_IN_PARTS(lsn)));
 | |
|   DBUG_ASSERT(LSN_OFFSET(lsn) % TRANSLOG_PAGE_SIZE != 0);
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   buff->lsn= lsn;
 | |
|   buff->groups_no= 0;
 | |
|   data.addr= &addr;
 | |
|   data.was_recovered= 0;
 | |
|   addr= lsn;
 | |
|   addr-= page_offset; /* offset decreasing */
 | |
|   res= (!(page= translog_get_page(&data, psize_buff.buffer, &direct_link))) ?
 | |
|     RECHEADER_READ_ERROR :
 | |
|     translog_read_record_header_from_buffer(page, page_offset, buff, 0);
 | |
|   translog_free_link(direct_link);
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Read record header and some fixed part of a record (the part depend
 | |
|    on record type).
 | |
| 
 | |
|    @param scan            scanner position to read
 | |
|    @param buff            log record header buffer
 | |
|    @param move_scanner    request to move scanner to the header position
 | |
| 
 | |
|    @note Some type of record can be read completely by this call
 | |
|    @note "Decoded" header stored in TRANSLOG_HEADER_BUFFER::header (relative
 | |
|    LSN can be translated to absolute one), some fields can be added (like
 | |
|    actual header length in the record if the header has variable length)
 | |
| 
 | |
|    @return Length of header or operation status
 | |
|      @retval RECHEADER_READ_ERROR  error
 | |
|      @retval #                     number of bytes in
 | |
|                                    TRANSLOG_HEADER_BUFFER::header where stored
 | |
|                                    decoded part of the header
 | |
| */
 | |
| 
 | |
| int translog_read_record_header_scan(TRANSLOG_SCANNER_DATA *scanner,
 | |
|                                      TRANSLOG_HEADER_BUFFER *buff,
 | |
|                                      my_bool move_scanner)
 | |
| {
 | |
|   translog_size_t res;
 | |
|   DBUG_ENTER("translog_read_record_header_scan");
 | |
|   DBUG_PRINT("enter", ("Scanner: Cur: " LSN_FMT "  Hrz: " LSN_FMT "  "
 | |
|                        "Lst: " LSN_FMT "  Offset: %u(%x)  fixed %d",
 | |
|                        LSN_IN_PARTS(scanner->page_addr),
 | |
|                        LSN_IN_PARTS(scanner->horizon),
 | |
|                        LSN_IN_PARTS(scanner->last_file_page),
 | |
|                        (uint) scanner->page_offset,
 | |
|                        (uint) scanner->page_offset, scanner->fixed_horizon));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
|   buff->groups_no= 0;
 | |
|   buff->lsn= scanner->page_addr;
 | |
|   buff->lsn+= scanner->page_offset; /* offset increasing */
 | |
|   res= translog_read_record_header_from_buffer(scanner->page,
 | |
|                                                scanner->page_offset,
 | |
|                                                buff,
 | |
|                                                (move_scanner ?
 | |
|                                                 scanner : 0));
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Read record header and some fixed part of the next record (the part
 | |
|    depend on record type).
 | |
| 
 | |
|    @param scanner         data for scanning if lsn is NULL scanner data
 | |
|                           will be used for continue scanning.
 | |
|                           The scanner can be NULL.
 | |
| 
 | |
|    @param buff            log record header buffer
 | |
| 
 | |
|    @return Length of header or operation status
 | |
|      @retval RECHEADER_READ_ERROR  error
 | |
|      @retval RECHEADER_READ_EOF    EOF
 | |
|      @retval #                     number of bytes in
 | |
|                                    TRANSLOG_HEADER_BUFFER::header where
 | |
|                                    stored decoded part of the header
 | |
| */
 | |
| 
 | |
| int translog_read_next_record_header(TRANSLOG_SCANNER_DATA *scanner,
 | |
|                                      TRANSLOG_HEADER_BUFFER *buff)
 | |
| {
 | |
|   translog_size_t res;
 | |
| 
 | |
|   DBUG_ENTER("translog_read_next_record_header");
 | |
|   buff->groups_no= 0;        /* to be sure that we will free it right */
 | |
|   DBUG_PRINT("enter", ("scanner: %p", scanner));
 | |
|   DBUG_PRINT("info", ("Scanner: Cur: " LSN_FMT "  Hrz: " LSN_FMT "  "
 | |
|                       "Lst: " LSN_FMT "  Offset: %u(%x)  fixed: %d",
 | |
|                       LSN_IN_PARTS(scanner->page_addr),
 | |
|                       LSN_IN_PARTS(scanner->horizon),
 | |
|                       LSN_IN_PARTS(scanner->last_file_page),
 | |
|                       (uint) scanner->page_offset,
 | |
|                       (uint) scanner->page_offset, scanner->fixed_horizon));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   do
 | |
|   {
 | |
|     if (translog_get_next_chunk(scanner))
 | |
|       DBUG_RETURN(RECHEADER_READ_ERROR);
 | |
|     if (scanner->page == END_OF_LOG)
 | |
|     {
 | |
|        DBUG_PRINT("info", ("End of file from the scanner"));
 | |
|        /* Last record was read */
 | |
|        buff->lsn= LSN_IMPOSSIBLE;
 | |
|        DBUG_RETURN(RECHEADER_READ_EOF);
 | |
|     }
 | |
|     DBUG_PRINT("info", ("Page: " LSN_FMT "  offset: %lu  byte: %x",
 | |
|                         LSN_IN_PARTS(scanner->page_addr),
 | |
|                         (ulong) scanner->page_offset,
 | |
|                         (uint) scanner->page[scanner->page_offset]));
 | |
|   } while (!translog_is_LSN_chunk(scanner->page[scanner->page_offset]) &&
 | |
|            scanner->page[scanner->page_offset] != TRANSLOG_FILLER);
 | |
| 
 | |
|   if (scanner->page[scanner->page_offset] == TRANSLOG_FILLER)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("End of file"));
 | |
|     /* Last record was read */
 | |
|     buff->lsn= LSN_IMPOSSIBLE;
 | |
|     /* Return 'end of log' marker */
 | |
|     res= RECHEADER_READ_EOF;
 | |
|   }
 | |
|   else
 | |
|     res= translog_read_record_header_scan(scanner, buff, 0);
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Moves record data reader to the next chunk and fill the data reader
 | |
|   information about that chunk.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_record_read_next_chunk()
 | |
|     data                 data cursor
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_record_read_next_chunk(TRANSLOG_READER_DATA *data)
 | |
| {
 | |
|   translog_size_t new_current_offset= data->current_offset + data->chunk_size;
 | |
|   uint16 chunk_header_len, chunk_len;
 | |
|   uint8 type;
 | |
|   DBUG_ENTER("translog_record_read_next_chunk");
 | |
| 
 | |
|   if (data->eor)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("end of the record flag set"));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   if (data->header.groups_no &&
 | |
|       data->header.groups_no - 1 != data->current_group &&
 | |
|       data->header.groups[data->current_group].num == data->current_chunk)
 | |
|   {
 | |
|     /* Goto next group */
 | |
|     data->current_group++;
 | |
|     data->current_chunk= 0;
 | |
|     DBUG_PRINT("info", ("skip to group: #%u", data->current_group));
 | |
|     translog_destroy_scanner(&data->scanner);
 | |
|     translog_scanner_init(data->header.groups[data->current_group].addr,
 | |
|                           1, &data->scanner, 1);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     data->current_chunk++;
 | |
|     if (translog_get_next_chunk(&data->scanner))
 | |
|       DBUG_RETURN(1);
 | |
|      if (data->scanner.page == END_OF_LOG)
 | |
|      {
 | |
|        /*
 | |
|          Actually it should not happened, but we want to quit nicely in case
 | |
|          of a truncated log
 | |
|        */
 | |
|        DBUG_RETURN(1);
 | |
|      }
 | |
|   }
 | |
|   type= data->scanner.page[data->scanner.page_offset] & TRANSLOG_CHUNK_TYPE;
 | |
| 
 | |
|   if (type == TRANSLOG_CHUNK_LSN && data->header.groups_no)
 | |
|   {
 | |
|     DBUG_PRINT("info",
 | |
|                ("Last chunk: data len: %u  offset: %u  group: %u of %u",
 | |
|                 data->header.chunk0_data_len, data->scanner.page_offset,
 | |
|                 data->current_group, data->header.groups_no - 1));
 | |
|     DBUG_ASSERT(data->header.groups_no - 1 == data->current_group);
 | |
|     DBUG_ASSERT(data->header.lsn ==
 | |
|                 data->scanner.page_addr + data->scanner.page_offset);
 | |
|     translog_destroy_scanner(&data->scanner);
 | |
|     translog_scanner_init(data->header.chunk0_data_addr, 1, &data->scanner, 1);
 | |
|     data->chunk_size= data->header.chunk0_data_len;
 | |
|     data->body_offset= data->scanner.page_offset;
 | |
|     data->current_offset= new_current_offset;
 | |
|     data->eor= 1;
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   if (type == TRANSLOG_CHUNK_LSN || type == TRANSLOG_CHUNK_FIXED)
 | |
|   {
 | |
|     data->eor= 1;
 | |
|     DBUG_RETURN(1);                             /* End of record */
 | |
|   }
 | |
| 
 | |
|   chunk_header_len=
 | |
|     translog_get_chunk_header_length(data->scanner.page +
 | |
|                                      data->scanner.page_offset);
 | |
|   chunk_len= translog_get_total_chunk_length(data->scanner.page,
 | |
|                                              data->scanner.page_offset);
 | |
|   data->chunk_size= chunk_len - chunk_header_len;
 | |
|   data->body_offset= data->scanner.page_offset + chunk_header_len;
 | |
|   data->current_offset= new_current_offset;
 | |
|   DBUG_PRINT("info", ("grp: %u  chunk: %u  body_offset: %u  chunk_size: %u  "
 | |
|                       "current_offset: %lu",
 | |
|                       (uint) data->current_group,
 | |
|                       (uint) data->current_chunk,
 | |
|                       (uint) data->body_offset,
 | |
|                       (uint) data->chunk_size, (ulong) data->current_offset));
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Initialize record reader data from LSN
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_init_reader_data()
 | |
|     lsn                  reference to LSN we should start from
 | |
|     data                 reader data to initialize
 | |
| 
 | |
|   RETURN
 | |
|     0  OK
 | |
|     1  Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_init_reader_data(LSN lsn,
 | |
|                                          TRANSLOG_READER_DATA *data)
 | |
| {
 | |
|   int read_header;
 | |
|   DBUG_ENTER("translog_init_reader_data");
 | |
|   if (translog_scanner_init(lsn, 1, &data->scanner, 1) ||
 | |
|       ((read_header=
 | |
|         translog_read_record_header_scan(&data->scanner, &data->header, 1))
 | |
|        == RECHEADER_READ_ERROR))
 | |
|     DBUG_RETURN(1);
 | |
|   data->read_header= read_header;
 | |
|   data->body_offset= data->header.non_header_data_start_offset;
 | |
|   data->chunk_size= data->header.non_header_data_len;
 | |
|   data->current_offset= data->read_header;
 | |
|   data->current_group= 0;
 | |
|   data->current_chunk= 0;
 | |
|   data->eor= 0;
 | |
|   DBUG_PRINT("info", ("read_header: %u  "
 | |
|                       "body_offset: %u  chunk_size: %u  current_offset: %lu",
 | |
|                       (uint) data->read_header,
 | |
|                       (uint) data->body_offset,
 | |
|                       (uint) data->chunk_size, (ulong) data->current_offset));
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Destroy reader data object
 | |
| */
 | |
| 
 | |
| static void translog_destroy_reader_data(TRANSLOG_READER_DATA *data)
 | |
| {
 | |
|   translog_destroy_scanner(&data->scanner);
 | |
|   translog_free_record_header(&data->header);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Read a part of the record.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     translog_read_record_header()
 | |
|     lsn                  log record serial number (address of the record)
 | |
|     offset               From the beginning of the record beginning (read
 | |
|                          by translog_read_record_header).
 | |
|     length               Length of record part which have to be read.
 | |
|     buffer               Buffer where to read the record part (have to be at
 | |
|                          least 'length' bytes length)
 | |
| 
 | |
|   RETURN
 | |
|     length of data actually read
 | |
| */
 | |
| 
 | |
| translog_size_t translog_read_record(LSN lsn,
 | |
|                                      translog_size_t offset,
 | |
|                                      translog_size_t length,
 | |
|                                      uchar *buffer,
 | |
|                                      TRANSLOG_READER_DATA *data)
 | |
| {
 | |
|   translog_size_t requested_length= length;
 | |
|   translog_size_t end= offset + length;
 | |
|   TRANSLOG_READER_DATA internal_data;
 | |
|   DBUG_ENTER("translog_read_record");
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   if (data == NULL)
 | |
|   {
 | |
|     DBUG_ASSERT(lsn != LSN_IMPOSSIBLE);
 | |
|     data= &internal_data;
 | |
|   }
 | |
|   if (lsn ||
 | |
|       (offset < data->current_offset &&
 | |
|        !(offset < data->read_header && offset + length < data->read_header)))
 | |
|   {
 | |
|     if (translog_init_reader_data(lsn, data))
 | |
|       DBUG_RETURN(0);
 | |
|   }
 | |
|   DBUG_PRINT("info", ("Offset: %lu  length: %lu  "
 | |
|                       "Scanner: Cur: " LSN_FMT "  Hrz: " LSN_FMT "  "
 | |
|                       "Lst: " LSN_FMT "  Offset: %u(%x)  fixed: %d",
 | |
|                       (ulong) offset, (ulong) length,
 | |
|                       LSN_IN_PARTS(data->scanner.page_addr),
 | |
|                       LSN_IN_PARTS(data->scanner.horizon),
 | |
|                       LSN_IN_PARTS(data->scanner.last_file_page),
 | |
|                       (uint) data->scanner.page_offset,
 | |
|                       (uint) data->scanner.page_offset,
 | |
|                       data->scanner.fixed_horizon));
 | |
|   if (offset < data->read_header)
 | |
|   {
 | |
|     uint16 len= MY_MIN(data->read_header, end) - offset;
 | |
|     DBUG_PRINT("info",
 | |
|                ("enter header offset: %lu  length: %lu",
 | |
|                 (ulong) offset, (ulong) length));
 | |
|     memcpy(buffer, data->header.header + offset, len);
 | |
|     length-= len;
 | |
|     if (length == 0)
 | |
|     {
 | |
|       translog_destroy_reader_data(data);
 | |
|       DBUG_RETURN(requested_length);
 | |
|     }
 | |
|     offset+= len;
 | |
|     buffer+= len;
 | |
|     DBUG_PRINT("info",
 | |
|                ("len: %u  offset: %lu   curr: %lu  length: %lu",
 | |
|                 len, (ulong) offset, (ulong) data->current_offset,
 | |
|                 (ulong) length));
 | |
|   }
 | |
|   /* TODO: find first page which we should read by offset */
 | |
| 
 | |
|   /* read the record chunk by chunk */
 | |
|   for(;;)
 | |
|   {
 | |
|     uint page_end= data->current_offset + data->chunk_size;
 | |
|     DBUG_PRINT("info",
 | |
|                ("enter body offset: %lu  curr: %lu  "
 | |
|                 "length: %lu  page_end: %lu",
 | |
|                 (ulong) offset, (ulong) data->current_offset, (ulong) length,
 | |
|                 (ulong) page_end));
 | |
|     if (offset < page_end)
 | |
|     {
 | |
|       uint len= page_end - offset;
 | |
|       set_if_smaller(len, length); /* in case we read beyond record's end */
 | |
|       DBUG_ASSERT(offset >= data->current_offset);
 | |
|       memcpy(buffer,
 | |
|               data->scanner.page + data->body_offset +
 | |
|               (offset - data->current_offset), len);
 | |
|       length-= len;
 | |
|       if (length == 0)
 | |
|       {
 | |
|         translog_destroy_reader_data(data);
 | |
|         DBUG_RETURN(requested_length);
 | |
|       }
 | |
|       offset+= len;
 | |
|       buffer+= len;
 | |
|       DBUG_PRINT("info",
 | |
|                  ("len: %u  offset: %lu  curr: %lu  length: %lu",
 | |
|                   len, (ulong) offset, (ulong) data->current_offset,
 | |
|                   (ulong) length));
 | |
|     }
 | |
|     if (translog_record_read_next_chunk(data))
 | |
|     {
 | |
|       translog_destroy_reader_data(data);
 | |
|       DBUG_RETURN(requested_length - length);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Force skipping to the next buffer
 | |
| 
 | |
|   @todo Do not copy old page content if all page protections are switched off
 | |
|   (because we do not need calculate something or change old parts of the page)
 | |
| */
 | |
| 
 | |
| static void translog_force_current_buffer_to_finish()
 | |
| {
 | |
|   TRANSLOG_ADDRESS new_buff_beginning;
 | |
|   uint16 old_buffer_no= log_descriptor.bc.buffer_no;
 | |
|   uint16 new_buffer_no= (old_buffer_no + 1) % TRANSLOG_BUFFERS_NO;
 | |
|   struct st_translog_buffer *new_buffer= (log_descriptor.buffers +
 | |
|                                           new_buffer_no);
 | |
|   struct st_translog_buffer *old_buffer= log_descriptor.bc.buffer;
 | |
|   uchar *data= log_descriptor.bc.ptr - log_descriptor.bc.current_page_fill;
 | |
|   uint16 left= TRANSLOG_PAGE_SIZE - log_descriptor.bc.current_page_fill;
 | |
|   uint16 UNINIT_VAR(current_page_fill), write_counter, previous_offset;
 | |
|   DBUG_ENTER("translog_force_current_buffer_to_finish");
 | |
| 
 | |
|   DBUG_PRINT("enter", ("Buffer #%u %p  "
 | |
|                        "Buffer addr: " LSN_FMT "  "
 | |
|                        "Page addr: " LSN_FMT "  "
 | |
|                        "size: %lu (%lu)  Pg: %u  left: %u  in progress %u",
 | |
|                        (uint) old_buffer_no,
 | |
|                        old_buffer,
 | |
|                        LSN_IN_PARTS(old_buffer->offset),
 | |
|                        LSN_FILE_NO(log_descriptor.horizon),
 | |
|                        (uint)(LSN_OFFSET(log_descriptor.horizon) -
 | |
|                                 log_descriptor.bc.current_page_fill),
 | |
|                        (ulong) old_buffer->size,
 | |
|                        (ulong) (log_descriptor.bc.ptr -log_descriptor.bc.
 | |
|                                 buffer->buffer),
 | |
|                        (uint) log_descriptor.bc.current_page_fill,
 | |
|                        (uint) left,
 | |
|                        (uint) old_buffer->
 | |
|                        copy_to_buffer_in_progress));
 | |
|   translog_lock_assert_owner();
 | |
|   new_buff_beginning= old_buffer->offset;
 | |
|   new_buff_beginning+= old_buffer->size; /* increase offset */
 | |
| 
 | |
|   DBUG_ASSERT(log_descriptor.bc.ptr !=NULL);
 | |
|   DBUG_ASSERT(LSN_FILE_NO(log_descriptor.horizon) ==
 | |
|               LSN_FILE_NO(old_buffer->offset) ||
 | |
|               translog_status == TRANSLOG_READONLY );
 | |
|   translog_check_cursor(&log_descriptor.bc);
 | |
|   DBUG_ASSERT(left < TRANSLOG_PAGE_SIZE);
 | |
|   if (left)
 | |
|   {
 | |
|     /*
 | |
|        TODO: if 'left' is so small that can't hold any other record
 | |
|        then do not move the page
 | |
|     */
 | |
|     DBUG_PRINT("info", ("left: %u", (uint) left));
 | |
| 
 | |
|     old_buffer->pre_force_close_horizon=
 | |
|       old_buffer->offset + old_buffer->size;
 | |
|     /* decrease offset */
 | |
|     new_buff_beginning-= log_descriptor.bc.current_page_fill;
 | |
|     current_page_fill= log_descriptor.bc.current_page_fill;
 | |
| 
 | |
|     memset(log_descriptor.bc.ptr, TRANSLOG_FILLER, left);
 | |
|     old_buffer->size+= left;
 | |
|     DBUG_PRINT("info", ("Finish Page buffer #%u: %p  "
 | |
|                         "Size: %lu",
 | |
|                         (uint) old_buffer->buffer_no,
 | |
|                         old_buffer,
 | |
|                         (ulong) old_buffer->size));
 | |
|     DBUG_ASSERT(old_buffer->buffer_no ==
 | |
|                 log_descriptor.bc.buffer_no);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     log_descriptor.bc.current_page_fill= 0;
 | |
|   }
 | |
| 
 | |
|   translog_buffer_lock(new_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|   {
 | |
|     TRANSLOG_ADDRESS offset= new_buffer->offset;
 | |
|     TRANSLOG_FILE *file= new_buffer->file;
 | |
|     uint8 ver= new_buffer->ver;
 | |
|     translog_lock_assert_owner();
 | |
| #endif
 | |
|     translog_wait_for_buffer_free(new_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|     /* We keep the handler locked so nobody can start this new buffer */
 | |
|     DBUG_ASSERT(offset == new_buffer->offset && new_buffer->file == NULL &&
 | |
|                 (file == NULL ? ver : (uint8)(ver + 1)) == new_buffer->ver);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   write_counter= log_descriptor.bc.write_counter;
 | |
|   previous_offset= log_descriptor.bc.previous_offset;
 | |
|   translog_start_buffer(new_buffer, &log_descriptor.bc, new_buffer_no);
 | |
|   /* Fix buffer offset (which was incorrectly set to horizon) */
 | |
|   log_descriptor.bc.buffer->offset= new_buff_beginning;
 | |
|   log_descriptor.bc.write_counter= write_counter;
 | |
|   log_descriptor.bc.previous_offset= previous_offset;
 | |
|   new_buffer->prev_last_lsn= BUFFER_MAX_LSN(old_buffer);
 | |
|   DBUG_PRINT("info", ("prev_last_lsn set to " LSN_FMT "  buffer: %p",
 | |
|                       LSN_IN_PARTS(new_buffer->prev_last_lsn),
 | |
|                       new_buffer));
 | |
| 
 | |
|   /*
 | |
|     Advances this log pointer, increases writers and let other threads to
 | |
|     write to the log while we process old page content
 | |
|   */
 | |
|   if (left)
 | |
|   {
 | |
|     log_descriptor.bc.ptr+= current_page_fill;
 | |
|     log_descriptor.bc.buffer->size= log_descriptor.bc.current_page_fill=
 | |
|       current_page_fill;
 | |
|     new_buffer->overlay= 1;
 | |
|   }
 | |
|   else
 | |
|     translog_new_page_header(&log_descriptor.horizon, &log_descriptor.bc);
 | |
|   translog_buffer_increase_writers(new_buffer);
 | |
|   translog_buffer_unlock(new_buffer);
 | |
| 
 | |
|   /*
 | |
|     We have to wait until all writers finish before start changing the
 | |
|     pages by applying protection and copying the page content in the
 | |
|     new buffer.
 | |
|   */
 | |
| #ifndef DBUG_OFF
 | |
|   {
 | |
|     TRANSLOG_ADDRESS offset= old_buffer->offset;
 | |
|     TRANSLOG_FILE *file= old_buffer->file;
 | |
|     uint8 ver= old_buffer->ver;
 | |
| #endif
 | |
|     /*
 | |
|       Now only one thread can flush log (buffer can flush many threads but
 | |
|       log flush log flush where this function is used can do only one thread)
 | |
|       so no other thread can set is_closing_buffer.
 | |
|     */
 | |
|     DBUG_ASSERT(!old_buffer->is_closing_buffer);
 | |
|     old_buffer->is_closing_buffer= 1; /* Other flushes will wait */
 | |
|     DBUG_PRINT("enter", ("Buffer #%u %p is_closing_buffer set",
 | |
|                          (uint) old_buffer->buffer_no, old_buffer));
 | |
|     translog_wait_for_writers(old_buffer);
 | |
| #ifndef DBUG_OFF
 | |
|     /* We blocked flushing this buffer so the buffer should not changed */
 | |
|     DBUG_ASSERT(offset == old_buffer->offset && file == old_buffer->file &&
 | |
|                 ver == old_buffer->ver);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (log_descriptor.flags & TRANSLOG_SECTOR_PROTECTION)
 | |
|   {
 | |
|     translog_put_sector_protection(data, &log_descriptor.bc);
 | |
|     if (left)
 | |
|     {
 | |
|       log_descriptor.bc.write_counter++;
 | |
|       log_descriptor.bc.previous_offset= current_page_fill;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       DBUG_PRINT("info", ("drop write_counter"));
 | |
|       log_descriptor.bc.write_counter= 0;
 | |
|       log_descriptor.bc.previous_offset= 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (log_descriptor.flags & TRANSLOG_PAGE_CRC)
 | |
|   {
 | |
|     uint32 crc= translog_crc(data + log_descriptor.page_overhead,
 | |
|                              TRANSLOG_PAGE_SIZE -
 | |
|                              log_descriptor.page_overhead);
 | |
|     DBUG_PRINT("info", ("CRC: 0x%x", crc));
 | |
|     int4store(data + 3 + 3 + 1, crc);
 | |
|   }
 | |
|   old_buffer->is_closing_buffer= 0;
 | |
|   DBUG_PRINT("enter", ("Buffer #%u %p  is_closing_buffer cleared",
 | |
|                        (uint) old_buffer->buffer_no, old_buffer));
 | |
|   mysql_cond_broadcast(&old_buffer->waiting_filling_buffer);
 | |
| 
 | |
|   if (left)
 | |
|   {
 | |
|     if (log_descriptor.flags &
 | |
|         (TRANSLOG_PAGE_CRC | TRANSLOG_SECTOR_PROTECTION))
 | |
|       memcpy(new_buffer->buffer, data, current_page_fill);
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         This page header does not change if we add more data to the page so
 | |
|         we can not copy it and will not overwrite later
 | |
|       */
 | |
|       new_buffer->skipped_data= current_page_fill;
 | |
|       TRASH_ALLOC(new_buffer->buffer, current_page_fill);
 | |
|       DBUG_ASSERT(new_buffer->skipped_data < TRANSLOG_PAGE_SIZE);
 | |
|     }
 | |
|   }
 | |
|   old_buffer->next_buffer_offset= new_buffer->offset;
 | |
|   translog_buffer_lock(new_buffer);
 | |
|   new_buffer->prev_buffer_offset= old_buffer->offset;
 | |
|   translog_buffer_decrease_writers(new_buffer);
 | |
|   translog_buffer_unlock(new_buffer);
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Waits while given lsn will be flushed
 | |
| 
 | |
|   @param  lsn            log record serial number up to which (inclusive)
 | |
|                          the log has to be flushed
 | |
| */
 | |
| 
 | |
| void  translog_flush_wait_for_end(LSN lsn)
 | |
| {
 | |
|   DBUG_ENTER("translog_flush_wait_for_end");
 | |
|   DBUG_PRINT("enter", ("LSN: " LSN_FMT, LSN_IN_PARTS(lsn)));
 | |
|   mysql_mutex_assert_owner(&log_descriptor.log_flush_lock);
 | |
|   while (cmp_translog_addr(log_descriptor.flushed, lsn) < 0)
 | |
|     mysql_cond_wait(&log_descriptor.log_flush_cond,
 | |
|                       &log_descriptor.log_flush_lock);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Sets goal for the next flush pass and waits for this pass end.
 | |
| 
 | |
|   @param  lsn            log record serial number up to which (inclusive)
 | |
|                          the log has to be flushed
 | |
| */
 | |
| 
 | |
| void translog_flush_set_new_goal_and_wait(TRANSLOG_ADDRESS lsn)
 | |
| {
 | |
|   int flush_no= log_descriptor.flush_no;
 | |
|   DBUG_ENTER("translog_flush_set_new_goal_and_wait");
 | |
|   DBUG_PRINT("enter", ("LSN: " LSN_FMT, LSN_IN_PARTS(lsn)));
 | |
|   mysql_mutex_assert_owner(&log_descriptor.log_flush_lock);
 | |
|   if (cmp_translog_addr(lsn, log_descriptor.next_pass_max_lsn) > 0)
 | |
|   {
 | |
|     log_descriptor.next_pass_max_lsn= lsn;
 | |
|     log_descriptor.max_lsn_requester= pthread_self();
 | |
|     mysql_cond_broadcast(&log_descriptor.new_goal_cond);
 | |
|   }
 | |
|   while (flush_no == log_descriptor.flush_no)
 | |
|   {
 | |
|     mysql_cond_wait(&log_descriptor.log_flush_cond,
 | |
|                       &log_descriptor.log_flush_lock);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief sync() range of files (inclusive) and directory (by request)
 | |
| 
 | |
|   @param min             min internal file number to flush
 | |
|   @param max             max internal file number to flush
 | |
|   @param sync_dir        need sync directory
 | |
| 
 | |
|   return Operation status
 | |
|     @retval 0      OK
 | |
|     @retval 1      Error
 | |
| */
 | |
| 
 | |
| static my_bool translog_sync_files(uint32 min, uint32 max,
 | |
|                                    my_bool sync_dir)
 | |
| {
 | |
|   uint fn;
 | |
|   my_bool rc= 0;
 | |
|   ulonglong flush_interval;
 | |
|   DBUG_ENTER("translog_sync_files");
 | |
|   DBUG_PRINT("info", ("min: %lu  max: %lu  sync dir: %d",
 | |
|                       (ulong) min, (ulong) max, (int) sync_dir));
 | |
|   DBUG_ASSERT(min <= max);
 | |
| 
 | |
|   flush_interval= group_commit_wait;
 | |
|   if (flush_interval)
 | |
|     flush_start= microsecond_interval_timer();
 | |
|   for (fn= min; fn <= max; fn++)
 | |
|   {
 | |
|     TRANSLOG_FILE *file= get_logfile_by_number(fn);
 | |
|     DBUG_ASSERT(file != NULL);
 | |
|     if (!file->is_sync)
 | |
|     {
 | |
|       if (mysql_file_sync(file->handler.file, MYF(MY_WME)))
 | |
|       {
 | |
|         rc= 1;
 | |
|         translog_stop_writing();
 | |
|         DBUG_RETURN(rc);
 | |
|       }
 | |
|       translog_syncs++;
 | |
|       file->is_sync= 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (sync_dir)
 | |
|   {
 | |
|     if (!(rc= sync_dir(log_descriptor.directory_fd,
 | |
|                        MYF(MY_WME | MY_IGNORE_BADFD))))
 | |
|       translog_syncs++;
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    check_skipped_lsn
 | |
| 
 | |
|    Check if lsn skipped in redo is ok
 | |
| */
 | |
| 
 | |
| void check_skipped_lsn(MARIA_HA *info, LSN lsn, my_bool index_file,
 | |
|                        pgcache_page_no_t page)
 | |
| {
 | |
|   if (lsn <= log_descriptor.horizon)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("Page is up to date, skipping redo"));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Give error, but don't flood the log */
 | |
|     if (skipped_lsn_err_count++ < MAX_LSN_ERRORS &&
 | |
|         ! info->s->redo_error_given++)
 | |
|     {
 | |
|       eprint(tracef, "Table %s has wrong LSN: " LSN_FMT " on page: %llu",
 | |
|              (index_file ? info->s->data_file_name.str :
 | |
|               info->s->index_file_name.str),
 | |
|              LSN_IN_PARTS(lsn), (ulonglong) page);
 | |
|       recovery_found_crashed_tables++;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief Flushes buffers with LSNs in them less or equal address <lsn>
 | |
| 
 | |
|   @param lsn             address up to which all LSNs should be flushed,
 | |
|                          can be reset to real last LSN address
 | |
|   @parem sent_to_disk    returns 'sent to disk' position
 | |
|   @param flush_horizon   returns horizon of the flush
 | |
| 
 | |
|   @note About terminology see comment to translog_flush().
 | |
| */
 | |
| 
 | |
| void translog_flush_buffers(TRANSLOG_ADDRESS *lsn,
 | |
|                                TRANSLOG_ADDRESS *sent_to_disk,
 | |
|                                TRANSLOG_ADDRESS *flush_horizon)
 | |
| {
 | |
|   dirty_buffer_mask_t dirty_buffer_mask;
 | |
|   uint i;
 | |
|   uint8 UNINIT_VAR(last_buffer_no), start_buffer_no;
 | |
|   DBUG_ENTER("translog_flush_buffers");
 | |
| 
 | |
|   /*
 | |
|     We will recheck information when will lock buffers one by
 | |
|     one so we can use unprotected read here (this is just for
 | |
|     speed up buffers processing)
 | |
|   */
 | |
|   dirty_buffer_mask= log_descriptor.dirty_buffer_mask;
 | |
|   DBUG_PRINT("info", ("Dirty buffer mask: %lx  current buffer: %u",
 | |
|                       (ulong) dirty_buffer_mask,
 | |
|                       (uint) log_descriptor.bc.buffer_no));
 | |
|   for (i= (log_descriptor.bc.buffer_no + 1) % TRANSLOG_BUFFERS_NO;
 | |
|        i != log_descriptor.bc.buffer_no && !(dirty_buffer_mask & (1 << i));
 | |
|        i= (i + 1) % TRANSLOG_BUFFERS_NO) {}
 | |
|   start_buffer_no= i;
 | |
| 
 | |
|   DBUG_PRINT("info",
 | |
|              ("start from: %u  current: %u  prev last lsn: " LSN_FMT,
 | |
|               (uint) start_buffer_no, (uint) log_descriptor.bc.buffer_no,
 | |
|               LSN_IN_PARTS(log_descriptor.bc.buffer->prev_last_lsn)));
 | |
| 
 | |
|   /*
 | |
|     if LSN up to which we have to flush bigger then maximum LSN of previous
 | |
|     buffer and at least one LSN was saved in the current buffer (last_lsn !=
 | |
|     LSN_IMPOSSIBLE) then we have to close the current buffer.
 | |
|   */
 | |
|   if (cmp_translog_addr(*lsn, log_descriptor.bc.buffer->prev_last_lsn) > 0 &&
 | |
|       log_descriptor.bc.buffer->last_lsn != LSN_IMPOSSIBLE)
 | |
|   {
 | |
|     struct st_translog_buffer *buffer= log_descriptor.bc.buffer;
 | |
|     *lsn= log_descriptor.bc.buffer->last_lsn; /* fix lsn if it was horizon */
 | |
|     DBUG_PRINT("info", ("LSN to flush fixed to last lsn: " LSN_FMT,
 | |
|                         LSN_IN_PARTS(*lsn)));
 | |
|     last_buffer_no= log_descriptor.bc.buffer_no;
 | |
|     log_descriptor.is_everything_flushed= 1;
 | |
|     translog_force_current_buffer_to_finish();
 | |
|     translog_buffer_unlock(buffer);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (log_descriptor.bc.buffer->last_lsn == LSN_IMPOSSIBLE &&
 | |
|         log_descriptor.bc.buffer->prev_last_lsn == LSN_IMPOSSIBLE)
 | |
|     {
 | |
|       DBUG_PRINT("info", ("There is no LSNs yet generated => do nothing"));
 | |
|       translog_unlock();
 | |
|       DBUG_VOID_RETURN;
 | |
|     }
 | |
| 
 | |
|     /* fix lsn if it was horizon */
 | |
|     *lsn= log_descriptor.bc.buffer->prev_last_lsn;
 | |
|     DBUG_PRINT("info", ("LSN to flush fixed to prev last lsn: " LSN_FMT,
 | |
|                LSN_IN_PARTS(*lsn)));
 | |
|     last_buffer_no= ((log_descriptor.bc.buffer_no + TRANSLOG_BUFFERS_NO -1) %
 | |
|                      TRANSLOG_BUFFERS_NO);
 | |
|     translog_unlock();
 | |
|   }
 | |
|   /* flush buffers */
 | |
|   *sent_to_disk= translog_get_sent_to_disk();
 | |
|   if (cmp_translog_addr(*lsn, *sent_to_disk) > 0)
 | |
|   {
 | |
| 
 | |
|     DBUG_PRINT("info", ("Start buffer #: %u  last buffer #: %u",
 | |
|                         (uint) start_buffer_no, (uint) last_buffer_no));
 | |
|     last_buffer_no= (last_buffer_no + 1) % TRANSLOG_BUFFERS_NO;
 | |
|     i= start_buffer_no;
 | |
|     do
 | |
|     {
 | |
|       struct st_translog_buffer *buffer= log_descriptor.buffers + i;
 | |
|       translog_buffer_lock(buffer);
 | |
|       DBUG_PRINT("info", ("Check buffer: %p  #: %u  "
 | |
|                           "prev last LSN: " LSN_FMT "  "
 | |
|                           "last LSN: " LSN_FMT "  status: %s",
 | |
|                           buffer,
 | |
|                           (uint) i,
 | |
|                           LSN_IN_PARTS(buffer->prev_last_lsn),
 | |
|                           LSN_IN_PARTS(buffer->last_lsn),
 | |
|                           (buffer->file ?
 | |
|                            "dirty" : "closed")));
 | |
|       if (buffer->prev_last_lsn <= *lsn &&
 | |
|           buffer->file != NULL)
 | |
|       {
 | |
|         DBUG_ASSERT(*flush_horizon <= buffer->offset + buffer->size);
 | |
|         *flush_horizon= (buffer->pre_force_close_horizon != LSN_IMPOSSIBLE ?
 | |
|                          buffer->pre_force_close_horizon :
 | |
|                          buffer->offset + buffer->size);
 | |
|         /* pre_force_close_horizon is reset during new buffer start */
 | |
|         DBUG_PRINT("info", ("flush_horizon: " LSN_FMT,
 | |
|                             LSN_IN_PARTS(*flush_horizon)));
 | |
|         DBUG_ASSERT(*flush_horizon <= log_descriptor.horizon);
 | |
| 
 | |
|         translog_buffer_flush(buffer);
 | |
|       }
 | |
|       translog_buffer_unlock(buffer);
 | |
|       i= (i + 1) % TRANSLOG_BUFFERS_NO;
 | |
|     } while (i != last_buffer_no);
 | |
|     *sent_to_disk= translog_get_sent_to_disk();
 | |
|   }
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief Flush the log up to given LSN (included)
 | |
| 
 | |
|   @param  lsn            log record serial number up to which (inclusive)
 | |
|                          the log has to be flushed
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval 0      OK
 | |
|     @retval 1      Error
 | |
| 
 | |
|   @note
 | |
| 
 | |
|   - Non group commit logic: Commits made in passes. Thread which started
 | |
|   flush first is performing actual flush, other threads sets new goal (LSN)
 | |
|   of the next pass (if it is maximum) and waits for the pass end or just
 | |
|   wait for the pass end.
 | |
| 
 | |
|   - If hard group commit enabled and rate set to zero:
 | |
|   The first thread sends all changed buffers to disk. This is repeated
 | |
|   as long as there are new LSNs added. The process can not loop
 | |
|   forever because we have limited number of threads and they will wait
 | |
|   for the data to be synced.
 | |
|   Pseudo code:
 | |
| 
 | |
|    do
 | |
|      send changed buffers to disk
 | |
|    while new_goal
 | |
|    sync
 | |
| 
 | |
|   - If hard group commit switched ON and less than rate microseconds has
 | |
|   passed from last sync, then after buffers have been sent to disk
 | |
|   wait until rate microseconds has passed since last sync, do sync and return.
 | |
|   This ensures that if we call sync infrequently we don't do any waits.
 | |
| 
 | |
|   - If soft group commit enabled everything works as with 'non group commit'
 | |
|   but the thread doesn't do any real sync(). If rate is not zero the
 | |
|   sync() will be performed by a service thread with the given rate
 | |
|   when needed (new LSN appears).
 | |
| 
 | |
|   @note Terminology:
 | |
|   'sent to disk' means written to disk but not sync()ed,
 | |
|   'flushed' mean sent to disk and synced().
 | |
| */
 | |
| 
 | |
| my_bool translog_flush(TRANSLOG_ADDRESS lsn)
 | |
| {
 | |
|   struct timespec abstime;
 | |
|   ulonglong UNINIT_VAR(flush_interval);
 | |
|   ulonglong time_spent;
 | |
|   LSN sent_to_disk= LSN_IMPOSSIBLE;
 | |
|   TRANSLOG_ADDRESS flush_horizon;
 | |
|   my_bool rc= 0;
 | |
|   my_bool hgroup_commit_at_start;
 | |
|   DBUG_ENTER("translog_flush");
 | |
|   DBUG_PRINT("enter", ("Flush up to LSN: " LSN_FMT, LSN_IN_PARTS(lsn)));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   mysql_mutex_lock(&log_descriptor.log_flush_lock);
 | |
|   DBUG_PRINT("info", ("Everything is flushed up to " LSN_FMT,
 | |
|                       LSN_IN_PARTS(log_descriptor.flushed)));
 | |
|   if (cmp_translog_addr(log_descriptor.flushed, lsn) >= 0)
 | |
|   {
 | |
|     mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
|   if (log_descriptor.flush_in_progress)
 | |
|   {
 | |
|     translog_lock();
 | |
|     /* fix lsn if it was horizon */
 | |
|     if (cmp_translog_addr(lsn, log_descriptor.bc.buffer->last_lsn) > 0)
 | |
|       lsn= BUFFER_MAX_LSN(log_descriptor.bc.buffer);
 | |
|     translog_unlock();
 | |
|     translog_flush_set_new_goal_and_wait(lsn);
 | |
|     if (!pthread_equal(log_descriptor.max_lsn_requester, pthread_self()))
 | |
|     {
 | |
|       /*
 | |
|         translog_flush_wait_for_end() release log_flush_lock while is
 | |
|         waiting then acquire it again
 | |
|       */
 | |
|       translog_flush_wait_for_end(lsn);
 | |
|       mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
|       DBUG_RETURN(0);
 | |
|     }
 | |
|     log_descriptor.next_pass_max_lsn= LSN_IMPOSSIBLE;
 | |
|   }
 | |
|   log_descriptor.flush_in_progress= 1;
 | |
|   flush_horizon= log_descriptor.previous_flush_horizon;
 | |
|   DBUG_PRINT("info", ("flush_in_progress is set, flush_horizon: " LSN_FMT,
 | |
|                       LSN_IN_PARTS(flush_horizon)));
 | |
|   mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
| 
 | |
|   hgroup_commit_at_start= hard_group_commit;
 | |
|   if (hgroup_commit_at_start)
 | |
|     flush_interval= group_commit_wait;
 | |
| 
 | |
|   translog_lock();
 | |
|   if (log_descriptor.is_everything_flushed)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("everything is flushed"));
 | |
|     translog_unlock();
 | |
|     mysql_mutex_lock(&log_descriptor.log_flush_lock);
 | |
|     goto out;
 | |
|   }
 | |
| 
 | |
|   for (;;)
 | |
|   {
 | |
|     /* Following function flushes buffers and makes translog_unlock() */
 | |
|     translog_flush_buffers(&lsn, &sent_to_disk, &flush_horizon);
 | |
| 
 | |
|     if (!hgroup_commit_at_start)
 | |
|       break;  /* flush pass is ended */
 | |
| 
 | |
| retest:
 | |
|     /*
 | |
|       We do not check time here because mysql_mutex_lock rarely takes
 | |
|       a lot of time so we can sacrifice a bit precision to performance
 | |
|       (taking into account that microsecond_interval_timer() might be
 | |
|       expensive call).
 | |
|     */
 | |
|     if (flush_interval == 0)
 | |
|       break;  /* flush pass is ended */
 | |
| 
 | |
|     mysql_mutex_lock(&log_descriptor.log_flush_lock);
 | |
|     if (log_descriptor.next_pass_max_lsn == LSN_IMPOSSIBLE)
 | |
|     {
 | |
|       if (flush_interval == 0 ||
 | |
|           (time_spent= (microsecond_interval_timer() - flush_start)) >=
 | |
|           flush_interval)
 | |
|       {
 | |
|         mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
|         break;
 | |
|       }
 | |
|       DBUG_PRINT("info", ("flush waits: %llu  interval: %llu  spent: %llu",
 | |
|                           flush_interval - time_spent,
 | |
|                           flush_interval, time_spent));
 | |
|       /* wait time or next goal */
 | |
|       set_timespec_nsec(abstime, flush_interval - time_spent);
 | |
|       mysql_cond_timedwait(&log_descriptor.new_goal_cond,
 | |
|                            &log_descriptor.log_flush_lock,
 | |
|                            &abstime);
 | |
|       mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
|       DBUG_PRINT("info", ("retest conditions"));
 | |
|       goto retest;
 | |
|     }
 | |
| 
 | |
|     /* take next goal */
 | |
|     lsn= log_descriptor.next_pass_max_lsn;
 | |
|     log_descriptor.next_pass_max_lsn= LSN_IMPOSSIBLE;
 | |
|     /* prevent other thread from continue */
 | |
|     log_descriptor.max_lsn_requester= pthread_self();
 | |
|     DBUG_PRINT("info", ("flush took next goal: " LSN_FMT,
 | |
|                         LSN_IN_PARTS(lsn)));
 | |
|     mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
| 
 | |
|     /* next flush pass */
 | |
|     DBUG_PRINT("info", ("next flush pass"));
 | |
|     translog_lock();
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     sync() files from previous flush till current one
 | |
|   */
 | |
|   if (!soft_sync || hgroup_commit_at_start)
 | |
|   {
 | |
|     if ((rc=
 | |
|          translog_sync_files(LSN_FILE_NO(log_descriptor.flushed),
 | |
|                              LSN_FILE_NO(lsn),
 | |
|                              sync_log_dir >= TRANSLOG_SYNC_DIR_ALWAYS &&
 | |
|                              (LSN_FILE_NO(log_descriptor.
 | |
|                                           previous_flush_horizon) !=
 | |
|                               LSN_FILE_NO(flush_horizon) ||
 | |
|                               (LSN_OFFSET(log_descriptor.
 | |
|                                           previous_flush_horizon) /
 | |
|                                TRANSLOG_PAGE_SIZE) !=
 | |
|                               (LSN_OFFSET(flush_horizon) /
 | |
|                                TRANSLOG_PAGE_SIZE)))))
 | |
|     {
 | |
|       sent_to_disk= LSN_IMPOSSIBLE;
 | |
|       mysql_mutex_lock(&log_descriptor.log_flush_lock);
 | |
|       goto out;
 | |
|     }
 | |
|     /* keep values for soft sync() and forced sync() actual */
 | |
|     {
 | |
|       uint32 fileno= LSN_FILE_NO(lsn);
 | |
|       soft_sync_min= fileno;
 | |
|       soft_sync_max= fileno;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     soft_sync_max= LSN_FILE_NO(lsn);
 | |
|     soft_need_sync= 1;
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(flush_horizon <= log_descriptor.horizon);
 | |
| 
 | |
|   mysql_mutex_lock(&log_descriptor.log_flush_lock);
 | |
|   log_descriptor.previous_flush_horizon= flush_horizon;
 | |
| out:
 | |
|   if (sent_to_disk != LSN_IMPOSSIBLE)
 | |
|     log_descriptor.flushed= sent_to_disk;
 | |
|   log_descriptor.flush_in_progress= 0;
 | |
|   log_descriptor.flush_no++;
 | |
|   DBUG_PRINT("info", ("flush_in_progress is dropped"));
 | |
|   mysql_mutex_unlock(&log_descriptor.log_flush_lock);
 | |
|   mysql_cond_broadcast(&log_descriptor.log_flush_cond);
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Gives a 2-byte-id to MARIA_SHARE and logs this fact
 | |
| 
 | |
|    If a MARIA_SHARE does not yet have a 2-byte-id (unique over all currently
 | |
|    open MARIA_SHAREs), give it one and record this assignment in the log
 | |
|    (LOGREC_FILE_ID log record).
 | |
| 
 | |
|    @param  tbl_info        table
 | |
|    @param  trn             calling transaction
 | |
| 
 | |
|    @return Operation status
 | |
|      @retval 0      OK
 | |
|      @retval 1      Error
 | |
| 
 | |
|    @note Can be called even if share already has an id (then will do nothing)
 | |
| */
 | |
| 
 | |
| int translog_assign_id_to_share(MARIA_HA *tbl_info, TRN *trn)
 | |
| {
 | |
|   uint16 id;
 | |
|   MARIA_SHARE *share= tbl_info->s;
 | |
|   /*
 | |
|     If you give an id to a non-BLOCK_RECORD table, you also need to release
 | |
|     this id somewhere. Then you can change the assertion.
 | |
|   */
 | |
|   DBUG_ASSERT(share->data_file_type == BLOCK_RECORD);
 | |
|   /* re-check under mutex to avoid having 2 ids for the same share */
 | |
|   mysql_mutex_lock(&share->intern_lock);
 | |
|   if (unlikely(share->id == 0))
 | |
|   {
 | |
|     LSN lsn;
 | |
|     LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2];
 | |
|     uchar log_data[FILEID_STORE_SIZE];
 | |
|     /* Inspired by set_short_trid() of trnman.c */
 | |
|     uint i= share->kfile.file % SHARE_ID_MAX + 1;
 | |
|     id= 0;
 | |
|     do
 | |
|     {
 | |
|       for ( ; i <= SHARE_ID_MAX ; i++) /* the range is [1..SHARE_ID_MAX] */
 | |
|       {
 | |
|         void *tmp= NULL;
 | |
|         if (id_to_share[i] == NULL &&
 | |
|             my_atomic_casptr((void **)&id_to_share[i], &tmp, share))
 | |
|         {
 | |
|           id= (uint16) i;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       i= 1; /* scan the whole array */
 | |
|     } while (id == 0);
 | |
|     DBUG_PRINT("info", ("id_to_share: %p -> %u", share, id));
 | |
|     fileid_store(log_data, id);
 | |
|     log_array[TRANSLOG_INTERNAL_PARTS + 0].str=    log_data;
 | |
|     log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data);
 | |
|     /*
 | |
|       open_file_name is an unresolved name (symlinks are not resolved, datadir
 | |
|       is not realpath-ed, etc) which is good: the log can be moved to another
 | |
|       directory and continue working.
 | |
|     */
 | |
|     log_array[TRANSLOG_INTERNAL_PARTS + 1].str=
 | |
|       (uchar *)share->open_file_name.str;
 | |
|     log_array[TRANSLOG_INTERNAL_PARTS + 1].length=
 | |
|       share->open_file_name.length + 1;
 | |
|     /*
 | |
|       We can't unlock share->intern_lock before the log entry is written to
 | |
|       ensure no one uses the id before it's logged.
 | |
|     */
 | |
|     if (unlikely(translog_write_record(&lsn, LOGREC_FILE_ID, trn, tbl_info,
 | |
|                                        (translog_size_t)
 | |
|                                        (sizeof(log_data) +
 | |
|                                         log_array[TRANSLOG_INTERNAL_PARTS +
 | |
|                                                   1].length),
 | |
|                                        sizeof(log_array)/sizeof(log_array[0]),
 | |
|                                        log_array, NULL, NULL)))
 | |
|     {
 | |
|       mysql_mutex_unlock(&share->intern_lock);
 | |
|       return 1;
 | |
|     }
 | |
|     /*
 | |
|       Now when translog record is done, we can set share->id.
 | |
|       If we set it before, then translog_write_record may pick up the id
 | |
|       before it's written to the log.
 | |
|     */
 | |
|     share->id= id;
 | |
|     share->state.logrec_file_id= lsn;
 | |
|   }
 | |
|   mysql_mutex_unlock(&share->intern_lock);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Recycles a MARIA_SHARE's short id.
 | |
| 
 | |
|    @param  share           table
 | |
| 
 | |
|    @note Must be called only if share has an id (i.e. id != 0)
 | |
| */
 | |
| 
 | |
| void translog_deassign_id_from_share(MARIA_SHARE *share)
 | |
| {
 | |
|   DBUG_PRINT("info", ("id_to_share: %p id %u -> 0",
 | |
|                       share, share->id));
 | |
|   /*
 | |
|     We don't need any mutex as we are called only when closing the last
 | |
|     instance of the table or at the end of REPAIR: no writes can be
 | |
|     happening. But a Checkpoint may be reading share->id, so we require this
 | |
|     mutex:
 | |
|   */
 | |
|   mysql_mutex_assert_owner(&share->intern_lock);
 | |
|   my_atomic_storeptr((void **)&id_to_share[share->id], 0);
 | |
|   share->id= 0;
 | |
|   /* useless but safety: */
 | |
|   share->lsn_of_file_id= LSN_IMPOSSIBLE;
 | |
| }
 | |
| 
 | |
| 
 | |
| void translog_assign_id_to_share_from_recovery(MARIA_SHARE *share,
 | |
|                                                uint16 id)
 | |
| {
 | |
|   DBUG_ASSERT(maria_in_recovery && !maria_multi_threaded);
 | |
|   DBUG_ASSERT(share->data_file_type == BLOCK_RECORD);
 | |
|   DBUG_ASSERT(share->id == 0);
 | |
|   DBUG_ASSERT(id_to_share[id] == NULL);
 | |
|   id_to_share[share->id= id]= share;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief check if such log file exists
 | |
| 
 | |
|    @param file_no number of the file to test
 | |
| 
 | |
|    @retval 0 no such file
 | |
|    @retval 1 there is file with such number
 | |
| */
 | |
| 
 | |
| my_bool translog_is_file(uint file_no)
 | |
| {
 | |
|   MY_STAT stat_buff;
 | |
|   char path[FN_REFLEN];
 | |
|   return (MY_TEST(mysql_file_stat(key_file_translog,
 | |
|                                   translog_filename_by_fileno(file_no, path),
 | |
|                                   &stat_buff, MYF(0))));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief returns minimum log file number
 | |
| 
 | |
|   @param horizon         the end of the log
 | |
|   @param is_protected    true if it is under purge_log protection
 | |
| 
 | |
|   @retval minimum file number
 | |
|   @retval 0 no files found
 | |
| */
 | |
| 
 | |
| static uint32 translog_first_file(TRANSLOG_ADDRESS horizon, int is_protected)
 | |
| {
 | |
|   uint min_file= 1, max_file;
 | |
|   DBUG_ENTER("translog_first_file");
 | |
|   if (!is_protected)
 | |
|     mysql_mutex_lock(&log_descriptor.purger_lock);
 | |
|   if (log_descriptor.min_file_number)
 | |
|   {
 | |
|     min_file= log_descriptor.min_file_number;
 | |
|     if (translog_is_file(log_descriptor.min_file_number))
 | |
|     {
 | |
|       DBUG_PRINT("info", ("cached %lu",
 | |
|                           (ulong) log_descriptor.min_file_number));
 | |
|       if (!is_protected)
 | |
|         mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|       DBUG_RETURN(log_descriptor.min_file_number);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   max_file= LSN_FILE_NO(horizon);
 | |
|   if (!translog_is_file(max_file))
 | |
|   {
 | |
|     if (!is_protected)
 | |
|       mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|     DBUG_RETURN(max_file);                      /* For compatibility */
 | |
|   }
 | |
| 
 | |
|   /* binary search for last file */
 | |
|   while (min_file < max_file)
 | |
|   {
 | |
|     uint test= (min_file + max_file) / 2;
 | |
|     DBUG_PRINT("info", ("min_file: %u  test: %u  max_file: %u",
 | |
|                         min_file, test, max_file));
 | |
|     if (translog_is_file(test))
 | |
|       max_file= test;
 | |
|     else
 | |
|       min_file= test+1;
 | |
|   }
 | |
|   log_descriptor.min_file_number= max_file;
 | |
|   if (!is_protected)
 | |
|     mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|   DBUG_PRINT("info", ("first file :%lu", (ulong) max_file));
 | |
|   DBUG_ASSERT(max_file >= 1);
 | |
|   DBUG_RETURN(max_file);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief returns the most close LSN higher the given chunk address
 | |
| 
 | |
|   @param addr the chunk address to start from
 | |
|   @param horizon the horizon if it is known or LSN_IMPOSSIBLE
 | |
| 
 | |
|   @retval LSN_ERROR Error
 | |
|   @retval LSN_IMPOSSIBLE no LSNs after the address
 | |
|   @retval # LSN of the most close LSN higher the given chunk address
 | |
| */
 | |
| 
 | |
| LSN translog_next_LSN(TRANSLOG_ADDRESS addr, TRANSLOG_ADDRESS horizon)
 | |
| {
 | |
|   TRANSLOG_SCANNER_DATA scanner;
 | |
|   LSN result;
 | |
|   DBUG_ENTER("translog_next_LSN");
 | |
| 
 | |
|   if (horizon == LSN_IMPOSSIBLE)
 | |
|     horizon= translog_get_horizon();
 | |
| 
 | |
|   if (addr == horizon)
 | |
|     DBUG_RETURN(LSN_IMPOSSIBLE);
 | |
| 
 | |
|   translog_scanner_init(addr, 0, &scanner, 1);
 | |
|   /*
 | |
|     addr can point not to a chunk beginning but page end so next
 | |
|     page beginning.
 | |
|   */
 | |
|   if (addr % TRANSLOG_PAGE_SIZE == 0)
 | |
|   {
 | |
|     /*
 | |
|       We are emulating the page end which cased such horizon value to
 | |
|       trigger translog_scanner_eop().
 | |
| 
 | |
|       We can't just increase addr on page header overhead because it
 | |
|       can be file end so we allow translog_get_next_chunk() to skip
 | |
|       to the next page in correct way
 | |
|     */
 | |
|     scanner.page_addr-= TRANSLOG_PAGE_SIZE;
 | |
|     scanner.page_offset= TRANSLOG_PAGE_SIZE;
 | |
| #ifndef DBUG_OFF
 | |
|     scanner.page= NULL; /* prevent using incorrect page content */
 | |
| #endif
 | |
|   }
 | |
|   /* addr can point not to a chunk beginning but to a page end */
 | |
|   if (translog_scanner_eop(&scanner))
 | |
|   {
 | |
|     if (translog_get_next_chunk(&scanner))
 | |
|     {
 | |
|       result= LSN_ERROR;
 | |
|       goto out;
 | |
|     }
 | |
|     if (scanner.page == END_OF_LOG)
 | |
|     {
 | |
|       result= LSN_IMPOSSIBLE;
 | |
|       goto out;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   while (!translog_is_LSN_chunk(scanner.page[scanner.page_offset]) &&
 | |
|          scanner.page[scanner.page_offset] != TRANSLOG_FILLER)
 | |
|   {
 | |
|     if (translog_get_next_chunk(&scanner))
 | |
|     {
 | |
|       result= LSN_ERROR;
 | |
|       goto out;
 | |
|     }
 | |
|     if (scanner.page == END_OF_LOG)
 | |
|     {
 | |
|       result= LSN_IMPOSSIBLE;
 | |
|       goto out;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (scanner.page[scanner.page_offset] == TRANSLOG_FILLER)
 | |
|     result= LSN_IMPOSSIBLE; /* reached page filler */
 | |
|   else
 | |
|     result= scanner.page_addr + scanner.page_offset;
 | |
| out:
 | |
|   translog_destroy_scanner(&scanner);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief returns the LSN of the first record starting in this log
 | |
| 
 | |
|    @retval LSN_ERROR Error
 | |
|    @retval LSN_IMPOSSIBLE no log or the log is empty
 | |
|    @retval # LSN of the first record
 | |
| */
 | |
| 
 | |
| LSN translog_first_lsn_in_log()
 | |
| {
 | |
|   TRANSLOG_ADDRESS addr, horizon= translog_get_horizon();
 | |
|   TRANSLOG_VALIDATOR_DATA data;
 | |
|   uint file;
 | |
|   uint16 chunk_offset;
 | |
|   uchar *page;
 | |
|   DBUG_ENTER("translog_first_lsn_in_log");
 | |
|   DBUG_PRINT("info", ("Horizon: " LSN_FMT, LSN_IN_PARTS(horizon)));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   if (!(file= translog_first_file(horizon, 0)))
 | |
|   {
 | |
|     /* log has no records yet */
 | |
|     DBUG_RETURN(LSN_IMPOSSIBLE);
 | |
|   }
 | |
| 
 | |
|   addr= MAKE_LSN(file, TRANSLOG_PAGE_SIZE); /* the first page of the file */
 | |
|   data.addr= &addr;
 | |
|   {
 | |
|     TRANSLOG_PAGE_SIZE_BUFF psize_buff;
 | |
|     if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL ||
 | |
|         (chunk_offset= translog_get_first_chunk_offset(page)) == 0)
 | |
|       DBUG_RETURN(LSN_ERROR);
 | |
|   }
 | |
|   addr+= chunk_offset;
 | |
| 
 | |
|   DBUG_RETURN(translog_next_LSN(addr, horizon));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief Returns theoretical first LSN if first log is present
 | |
| 
 | |
|    @retval LSN_ERROR Error
 | |
|    @retval LSN_IMPOSSIBLE no log
 | |
|    @retval # LSN of the first record
 | |
| */
 | |
| 
 | |
| LSN translog_first_theoretical_lsn()
 | |
| {
 | |
|   TRANSLOG_ADDRESS addr= translog_get_horizon();
 | |
|   TRANSLOG_PAGE_SIZE_BUFF psize_buff;
 | |
|   uchar *page;
 | |
|   TRANSLOG_VALIDATOR_DATA data;
 | |
|   DBUG_ENTER("translog_first_theoretical_lsn");
 | |
|   DBUG_PRINT("info", ("Horizon: " LSN_FMT, LSN_IN_PARTS(addr)));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   if (!translog_is_file(1))
 | |
|     DBUG_RETURN(LSN_IMPOSSIBLE);
 | |
|   if (addr == MAKE_LSN(1, TRANSLOG_PAGE_SIZE))
 | |
|   {
 | |
|     /* log has no records yet */
 | |
|     DBUG_RETURN(MAKE_LSN(1, TRANSLOG_PAGE_SIZE +
 | |
|                          log_descriptor.page_overhead));
 | |
|   }
 | |
| 
 | |
|   addr= MAKE_LSN(1, TRANSLOG_PAGE_SIZE); /* the first page of the file */
 | |
|   data.addr= &addr;
 | |
|   if ((page= translog_get_page(&data, psize_buff.buffer, NULL)) == NULL)
 | |
|     DBUG_RETURN(LSN_ERROR);
 | |
| 
 | |
|   DBUG_RETURN(MAKE_LSN(1, TRANSLOG_PAGE_SIZE +
 | |
|                        page_overhead[page[TRANSLOG_PAGE_FLAGS]]));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Checks given low water mark and purge files if it is need
 | |
| 
 | |
|   @param low the last (minimum) address which is need
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| my_bool translog_purge(TRANSLOG_ADDRESS low)
 | |
| {
 | |
|   uint32 last_need_file= LSN_FILE_NO(low);
 | |
|   uint32 min_unsync;
 | |
|   int soft;
 | |
|   TRANSLOG_ADDRESS horizon= translog_get_horizon();
 | |
|   int rc= 0;
 | |
|   DBUG_ENTER("translog_purge");
 | |
|   DBUG_PRINT("enter", ("low: " LSN_FMT, LSN_IN_PARTS(low)));
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   soft= soft_sync;
 | |
|   min_unsync= soft_sync_min;
 | |
|   DBUG_PRINT("info", ("min_unsync: %lu", (ulong) min_unsync));
 | |
|   if (soft && min_unsync < last_need_file)
 | |
|   {
 | |
|     last_need_file= min_unsync;
 | |
|     DBUG_PRINT("info", ("last_need_file set to %lu", (ulong)last_need_file));
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&log_descriptor.purger_lock);
 | |
|   DBUG_PRINT("info", ("last_lsn_checked file: %lu:",
 | |
|                       (ulong) log_descriptor.last_lsn_checked));
 | |
|   if (LSN_FILE_NO(log_descriptor.last_lsn_checked) < last_need_file)
 | |
|   {
 | |
|     uint32 i;
 | |
|     uint32 min_file= translog_first_file(horizon, 1);
 | |
|     DBUG_ASSERT(min_file != 0); /* log is already started */
 | |
|     DBUG_PRINT("info", ("min_file:  %lu:",(ulong) min_file));
 | |
|     for(i= min_file; i < last_need_file && rc == 0; i++)
 | |
|     {
 | |
|       LSN lsn= translog_get_file_max_lsn_stored(i);
 | |
|       if (lsn == LSN_IMPOSSIBLE)
 | |
|         break;   /* files are still in writing */
 | |
|       if (lsn == LSN_ERROR)
 | |
|       {
 | |
|         rc= 1;
 | |
|         break;
 | |
|       }
 | |
|       if (cmp_translog_addr(lsn, low) >= 0)
 | |
|         break;
 | |
| 
 | |
|       DBUG_PRINT("info", ("purge file %lu", (ulong) i));
 | |
| 
 | |
|       /* remove file descriptor from the cache */
 | |
|       /*
 | |
|         log_descriptor.min_file can be changed only here during execution
 | |
|         and the function is serialized, so we can access it without problems
 | |
|       */
 | |
|       if (i >= log_descriptor.min_file)
 | |
|       {
 | |
|         TRANSLOG_FILE *file;
 | |
|         mysql_rwlock_wrlock(&log_descriptor.open_files_lock);
 | |
|         DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|                     log_descriptor.open_files.elements);
 | |
|         DBUG_ASSERT(log_descriptor.min_file == i);
 | |
|         file= *((TRANSLOG_FILE **)pop_dynamic(&log_descriptor.open_files));
 | |
|         DBUG_PRINT("info", ("Files : %zu", log_descriptor.open_files.elements));
 | |
|         DBUG_ASSERT(i == file->number);
 | |
|         log_descriptor.min_file++;
 | |
|         DBUG_ASSERT(log_descriptor.max_file - log_descriptor.min_file + 1 ==
 | |
|                     log_descriptor.open_files.elements);
 | |
|         mysql_rwlock_unlock(&log_descriptor.open_files_lock);
 | |
|         translog_close_log_file(file);
 | |
|       }
 | |
|       if (log_purge_type == TRANSLOG_PURGE_IMMIDIATE && ! log_purge_disabled)
 | |
|       {
 | |
|         char path[FN_REFLEN], *file_name;
 | |
|         file_name= translog_filename_by_fileno(i, path);
 | |
|         rc= MY_TEST(mysql_file_delete(key_file_translog,
 | |
|                                       file_name, MYF(MY_WME)));
 | |
|       }
 | |
|     }
 | |
|     if (unlikely(rc == 1))
 | |
|       log_descriptor.min_need_file= 0; /* impossible value */
 | |
|     else
 | |
|       log_descriptor.min_need_file= i;
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Purges files by stored min need file in case of
 | |
|     "one demand" purge type
 | |
| 
 | |
|   @note This function do real work only if it is "one demand" purge type
 | |
|     and translog_purge() was called at least once and last time without
 | |
|     errors
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 Error
 | |
| */
 | |
| 
 | |
| my_bool translog_purge_at_flush()
 | |
| {
 | |
|   uint32 i, min_file;
 | |
|   int rc= 0;
 | |
|   DBUG_ENTER("translog_purge_at_flush");
 | |
|   DBUG_ASSERT(translog_status == TRANSLOG_OK ||
 | |
|               translog_status == TRANSLOG_READONLY);
 | |
| 
 | |
|   if (unlikely(translog_status == TRANSLOG_READONLY))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("The log is read only => exit"));
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   if (log_purge_type != TRANSLOG_PURGE_ONDEMAND)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("It is not \"at_flush\" => exit"));
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&log_descriptor.purger_lock);
 | |
| 
 | |
|   if (unlikely(log_descriptor.min_need_file == 0 || log_purge_disabled))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("No info about min need file => exit"));
 | |
|     mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   min_file= translog_first_file(translog_get_horizon(), 1);
 | |
|   DBUG_ASSERT(min_file != 0); /* log is already started */
 | |
|   for(i= min_file; i < log_descriptor.min_need_file ; i++)
 | |
|   {
 | |
|     char path[FN_REFLEN], *file_name;
 | |
|     DBUG_PRINT("info", ("purge file %lu\n", (ulong) i));
 | |
|     file_name= translog_filename_by_fileno(i, path);
 | |
|     rc|= MY_TEST(mysql_file_delete(key_file_translog,
 | |
|                                   file_name, MYF(MY_WME)));
 | |
|     DBUG_ASSERT(rc == 0);
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Gets min file number
 | |
| 
 | |
|   @param horizon         the end of the log
 | |
| 
 | |
|   @retval minimum file number
 | |
|   @retval 0 no files found
 | |
| */
 | |
| 
 | |
| uint32 translog_get_first_file(TRANSLOG_ADDRESS horizon)
 | |
| {
 | |
|   return translog_first_file(horizon, 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Gets min file number which is needed
 | |
| 
 | |
|   @retval minimum file number
 | |
|   @retval 0 unknown
 | |
| */
 | |
| 
 | |
| uint32 translog_get_first_needed_file()
 | |
| {
 | |
|   uint32 file_no;
 | |
|   mysql_mutex_lock(&log_descriptor.purger_lock);
 | |
|   file_no= log_descriptor.min_need_file;
 | |
|   mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
|   return file_no;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Gets transaction log file size
 | |
| 
 | |
|   @return transaction log file size
 | |
| */
 | |
| 
 | |
| uint32 translog_get_file_size()
 | |
| {
 | |
|   uint32 res;
 | |
|   translog_lock();
 | |
|   res= log_descriptor.log_file_max_size;
 | |
|   translog_unlock();
 | |
|   return (res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Sets transaction log file size
 | |
| 
 | |
|   @return Returns actually set transaction log size
 | |
| */
 | |
| 
 | |
| void translog_set_file_size(uint32 size)
 | |
| {
 | |
|   struct st_translog_buffer *old_buffer= NULL;
 | |
|   DBUG_ENTER("translog_set_file_size");
 | |
|   translog_lock();
 | |
|   DBUG_PRINT("enter", ("Size: %lu", (ulong) size));
 | |
|   DBUG_ASSERT(size % TRANSLOG_PAGE_SIZE == 0);
 | |
|   DBUG_ASSERT(size >= TRANSLOG_MIN_FILE_SIZE);
 | |
|   log_descriptor.log_file_max_size= size;
 | |
|   /* if current file longer then finish it*/
 | |
|   if (LSN_OFFSET(log_descriptor.horizon) >=  log_descriptor.log_file_max_size)
 | |
|   {
 | |
|     old_buffer= log_descriptor.bc.buffer;
 | |
|     translog_buffer_next(&log_descriptor.horizon, &log_descriptor.bc, 1);
 | |
|     translog_buffer_unlock(old_buffer);
 | |
|   }
 | |
|   translog_unlock();
 | |
|   if (old_buffer)
 | |
|   {
 | |
|     translog_buffer_lock(old_buffer);
 | |
|     translog_buffer_flush(old_buffer);
 | |
|     translog_buffer_unlock(old_buffer);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    Write debug information to log if we EXTRA_DEBUG is enabled
 | |
| */
 | |
| 
 | |
| my_bool translog_log_debug_info(TRN *trn __attribute__((unused)),
 | |
|                                 enum translog_debug_info_type type
 | |
|                                 __attribute__((unused)),
 | |
|                                 uchar *info __attribute__((unused)),
 | |
|                                 size_t length __attribute__((unused)))
 | |
| {
 | |
| #ifdef EXTRA_DEBUG
 | |
|   LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2];
 | |
|   uchar debug_type;
 | |
|   LSN lsn;
 | |
| 
 | |
|   if (!trn)
 | |
|   {
 | |
|     /*
 | |
|       We can't log the current transaction because we don't have
 | |
|       an active transaction. Use a temporary transaction object instead
 | |
|     */
 | |
|     trn= &dummy_transaction_object;
 | |
|   }
 | |
|   debug_type= (uchar) type;
 | |
|   log_array[TRANSLOG_INTERNAL_PARTS + 0].str= &debug_type;
 | |
|   log_array[TRANSLOG_INTERNAL_PARTS + 0].length= 1;
 | |
|   log_array[TRANSLOG_INTERNAL_PARTS + 1].str= info;
 | |
|   log_array[TRANSLOG_INTERNAL_PARTS + 1].length= length;
 | |
|   return translog_write_record(&lsn, LOGREC_DEBUG_INFO,
 | |
|                                trn, NULL,
 | |
|                                (translog_size_t) (1+ length),
 | |
|                                sizeof(log_array)/sizeof(log_array[0]),
 | |
|                                log_array, NULL, NULL);
 | |
| #else
 | |
|   return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Sets soft sync mode
 | |
| 
 | |
|   @param mode            TRUE if we need switch soft sync on else off
 | |
| */
 | |
| 
 | |
| void translog_soft_sync(my_bool mode)
 | |
| {
 | |
|   soft_sync= mode;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Sets hard group commit
 | |
| 
 | |
|   @param mode            TRUE if we need switch hard group commit on else off
 | |
| */
 | |
| 
 | |
| void translog_hard_group_commit(my_bool mode)
 | |
| {
 | |
|   hard_group_commit= mode;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief forced log sync (used when we are switching modes)
 | |
| */
 | |
| 
 | |
| void translog_sync()
 | |
| {
 | |
|   DBUG_ENTER("ma_translog_sync");
 | |
| 
 | |
|   /* The following is only true if initalization of translog succeded */
 | |
|   if (log_descriptor.open_files.elements != 0)
 | |
|   {
 | |
|     uint32 max= get_current_logfile()->number;
 | |
|     uint32 min;
 | |
| 
 | |
|     min= soft_sync_min;
 | |
|     if (!min)
 | |
|       min= max;
 | |
| 
 | |
|     translog_sync_files(min, max, sync_log_dir >= TRANSLOG_SYNC_DIR_ALWAYS);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief set rate for group commit
 | |
| 
 | |
|   @param interval            interval to set.
 | |
| 
 | |
|   @note We use this function with additional variable because have to
 | |
|   restart service thread with new value which we can't make inside changing
 | |
|   variable routine (update_maria_group_commit_interval)
 | |
| */
 | |
| 
 | |
| void translog_set_group_commit_interval(uint32 interval)
 | |
| {
 | |
|   DBUG_ENTER("translog_set_group_commit_interval");
 | |
|   group_commit_wait= interval;
 | |
|   DBUG_PRINT("info", ("wait: %llu",
 | |
|                       (ulonglong)group_commit_wait));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief syncing service thread
 | |
| */
 | |
| 
 | |
| static pthread_handler_t
 | |
| ma_soft_sync_background( void *arg __attribute__((unused)))
 | |
| {
 | |
| 
 | |
|   my_thread_init();
 | |
|   {
 | |
|     DBUG_ENTER("ma_soft_sync_background");
 | |
|     for(;;)
 | |
|     {
 | |
|       ulonglong prev_loop= microsecond_interval_timer();
 | |
|       ulonglong time, sleep;
 | |
|       uint32 min, max, sync_request;
 | |
|       min= soft_sync_min;
 | |
|       max= soft_sync_max;
 | |
|       sync_request= soft_need_sync;
 | |
|       soft_sync_min= max;
 | |
|       soft_need_sync= 0;
 | |
| 
 | |
|       sleep= group_commit_wait;
 | |
|       if (sync_request)
 | |
|         translog_sync_files(min, max, FALSE);
 | |
|       time= microsecond_interval_timer() - prev_loop;
 | |
|       if (time > sleep)
 | |
|         sleep= 0;
 | |
|       else
 | |
|         sleep-= time;
 | |
|       if (my_service_thread_sleep(&soft_sync_control, sleep))
 | |
|         break;
 | |
|     }
 | |
|     my_thread_end();
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Starts syncing thread
 | |
| */
 | |
| 
 | |
| int translog_soft_sync_start(void)
 | |
| {
 | |
|   int res= 0;
 | |
|   uint32 min, max;
 | |
|   DBUG_ENTER("translog_soft_sync_start");
 | |
| 
 | |
|   /* check and init variables */
 | |
|   min= soft_sync_min;
 | |
|   max= soft_sync_max;
 | |
|   if (!max)
 | |
|     soft_sync_max= max= get_current_logfile()->number;
 | |
|   if (!min)
 | |
|     soft_sync_min= max;
 | |
|   soft_need_sync= 1;
 | |
| 
 | |
|   if (!(res= ma_service_thread_control_init(&soft_sync_control)))
 | |
|     if ((res= mysql_thread_create(key_thread_soft_sync,
 | |
|                                   &soft_sync_control.thread, NULL,
 | |
|                                   ma_soft_sync_background, NULL)))
 | |
|       soft_sync_control.killed= TRUE;
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Stops syncing thread
 | |
| */
 | |
| 
 | |
| void  translog_soft_sync_end(void)
 | |
| {
 | |
|   DBUG_ENTER("translog_soft_sync_end");
 | |
|   if (soft_sync_control.inited)
 | |
|   {
 | |
|     ma_service_thread_control_end(&soft_sync_control);
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Dump information about file header page.
 | |
| */
 | |
| 
 | |
| static void dump_header_page(uchar *buff)
 | |
| {
 | |
|   LOGHANDLER_FILE_INFO desc;
 | |
|   char strbuff[21];
 | |
|   struct tm tmp_tm;
 | |
|   time_t header_time;
 | |
| 
 | |
|   translog_interpret_file_header(&desc, buff);
 | |
|   header_time= desc.timestamp/1000000ULL;
 | |
|   localtime_r(&header_time, &tmp_tm);
 | |
| 
 | |
|   printf("  This can be header page:\n"
 | |
|          "    Timestamp: %04d.%02d.%02d %02d.%02d.%02d  (%s)\n"
 | |
|          "    Aria log version: %lu\n"
 | |
|          "    Server version: %lu\n"
 | |
|          "    Server id %lu\n"
 | |
|          "    Page size %lu\n",
 | |
|          tmp_tm.tm_year+1900, tmp_tm.tm_mon+1, tmp_tm.tm_mday,
 | |
|          tmp_tm.tm_hour, tmp_tm.tm_min, tmp_tm.tm_sec,
 | |
|          llstr(desc.timestamp, strbuff),
 | |
|          desc.maria_version,
 | |
|          desc.mysql_version,
 | |
|          desc.server_id,
 | |
|          desc.page_size);
 | |
|   if (desc.page_size != TRANSLOG_PAGE_SIZE)
 | |
|     printf("      WARNING: page size is not equal compiled in one %lu!!!\n",
 | |
|            (ulong) TRANSLOG_PAGE_SIZE);
 | |
|   printf("    File number %lu\n"
 | |
|          "    Max lsn: " LSN_FMT "\n",
 | |
|          desc.file_number,
 | |
|          LSN_IN_PARTS(desc.max_lsn));
 | |
| }
 | |
| 
 | |
| static const char *record_class_string[]=
 | |
| {
 | |
|   "LOGRECTYPE_NOT_ALLOWED",
 | |
|   "LOGRECTYPE_VARIABLE_LENGTH",
 | |
|   "LOGRECTYPE_PSEUDOFIXEDLENGTH",
 | |
|   "LOGRECTYPE_FIXEDLENGTH"
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief dump information about transaction log chunk
 | |
| 
 | |
|   @param buffer          reference to the whole page
 | |
|   @param ptr             pointer to the chunk
 | |
| 
 | |
|   @reval # reference to the next chunk
 | |
|   @retval NULL can't interpret data
 | |
| */
 | |
| 
 | |
| static uchar *dump_chunk(uchar *buffer, uchar *ptr)
 | |
| {
 | |
|   uint length;
 | |
|   if (*ptr == TRANSLOG_FILLER)
 | |
|   {
 | |
|     printf("  Filler till the page end\n");
 | |
|     for (; ptr < buffer + TRANSLOG_PAGE_SIZE; ptr++)
 | |
|     {
 | |
|       if (*ptr != TRANSLOG_FILLER)
 | |
|       {
 | |
|         printf("    WARNING: non filler character met before page end "
 | |
|                "(page + 0x%04x: 0x%02x) (stop interpretation)!!!",
 | |
|                (uint) (ptr - buffer), (uint) ptr[0]);
 | |
|         return NULL;
 | |
|       }
 | |
|     }
 | |
|     return ptr;
 | |
|   }
 | |
|   if (*ptr == 0 || *ptr == 0xFF)
 | |
|   {
 | |
|     printf("    WARNING: chunk can't start from 0x0 "
 | |
|            "(stop interpretation)!!!\n");
 | |
|     return NULL;
 | |
|   }
 | |
|   switch (ptr[0] & TRANSLOG_CHUNK_TYPE) {
 | |
|   case TRANSLOG_CHUNK_LSN:
 | |
|     printf("    LSN chunk type 0 (variable length)\n");
 | |
|     if (likely((ptr[0] & TRANSLOG_REC_TYPE) != TRANSLOG_CHUNK_0_CONT))
 | |
|     {
 | |
|       printf("      Record type %u: %s  record class %s compressed LSNs: %u\n",
 | |
|              ptr[0] & TRANSLOG_REC_TYPE,
 | |
|              (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name ?
 | |
|               log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name :
 | |
|               "NULL"),
 | |
|              record_class_string[log_record_type_descriptor[ptr[0] &
 | |
|                                                             TRANSLOG_REC_TYPE].
 | |
|                                                             rclass],
 | |
|              log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].
 | |
|              compressed_LSN);
 | |
|       if (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].rclass !=
 | |
|           LOGRECTYPE_VARIABLE_LENGTH)
 | |
|       {
 | |
|         printf("        WARNING: this record class here can't be used "
 | |
|                "(stop interpretation)!!!\n");
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       printf("      Continuation of previous chunk 0 header \n");
 | |
|     printf("      Short transaction id: %u\n", (uint) uint2korr(ptr + 1));
 | |
|     {
 | |
|       uchar *hdr_ptr= ptr + 1 + 2; /* chunk type and short trid */
 | |
|       uint16 chunk_len;
 | |
|       printf ("      Record length: %lu\n",
 | |
|               (ulong) translog_variable_record_1group_decode_len(&hdr_ptr));
 | |
|       chunk_len= uint2korr(hdr_ptr);
 | |
|       if (chunk_len == 0)
 | |
|         printf ("      It is 1 group record (chunk length == 0)\n");
 | |
|       else
 | |
|       {
 | |
|         uint16 groups, i;
 | |
| 
 | |
|         printf ("      Chunk length %u\n", (uint) chunk_len);
 | |
|         groups= uint2korr(hdr_ptr + 2);
 | |
|         hdr_ptr+= 4;
 | |
|         printf ("      Number of groups left to the end %u:\n", (uint) groups);
 | |
|         for(i= 0;
 | |
|             i < groups && hdr_ptr < buffer + TRANSLOG_PAGE_SIZE;
 | |
|             i++, hdr_ptr+= LSN_STORE_SIZE + 1)
 | |
|         {
 | |
|           TRANSLOG_ADDRESS gpr_addr= lsn_korr(hdr_ptr);
 | |
|           uint pages= hdr_ptr[LSN_STORE_SIZE];
 | |
|           printf ("        Group +#%u: " LSN_FMT "  pages: %u\n",
 | |
|                   (uint) i, LSN_IN_PARTS(gpr_addr), pages);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     break;
 | |
|   case TRANSLOG_CHUNK_FIXED:
 | |
|     printf("    LSN chunk type 1 (fixed size)\n");
 | |
|     printf("      Record type %u: %s  record class %s compressed LSNs: %u\n",
 | |
|            ptr[0] & TRANSLOG_REC_TYPE,
 | |
|            (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name ?
 | |
|             log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].name :
 | |
|             "NULL"),
 | |
|            record_class_string[log_record_type_descriptor[ptr[0] &
 | |
|                                                           TRANSLOG_REC_TYPE].
 | |
|                                                           rclass],
 | |
|            log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].
 | |
|            compressed_LSN);
 | |
|     if (log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].rclass !=
 | |
|         LOGRECTYPE_PSEUDOFIXEDLENGTH &&
 | |
|         log_record_type_descriptor[ptr[0] & TRANSLOG_REC_TYPE].rclass !=
 | |
|         LOGRECTYPE_FIXEDLENGTH)
 | |
|     {
 | |
|       printf("        WARNING: this record class here can't be used "
 | |
|              "(stop interpretation)!!!\n");
 | |
|     }
 | |
|     printf("      Short transaction id: %u\n", (uint) uint2korr(ptr + 1));
 | |
|     break;
 | |
|   case TRANSLOG_CHUNK_NOHDR:
 | |
|     printf("    No header chunk type 2(till the end of the page)\n");
 | |
|     if (ptr[0] & TRANSLOG_REC_TYPE)
 | |
|     {
 | |
|       printf("      WARNING: chunk header content record type: 0x%02x "
 | |
|              "(dtop interpretation)!!!",
 | |
|              (uint) ptr[0]);
 | |
|       return NULL;
 | |
|     }
 | |
|     break;
 | |
|   case TRANSLOG_CHUNK_LNGTH:
 | |
|     printf("    Chunk with length type 3\n");
 | |
|     if (ptr[0] & TRANSLOG_REC_TYPE)
 | |
|     {
 | |
|       printf("      WARNING: chunk header content record type: 0x%02x "
 | |
|              "(dtop interpretation)!!!",
 | |
|              (uint) ptr[0]);
 | |
|       return NULL;
 | |
|     }
 | |
|     break;
 | |
|   }
 | |
|   {
 | |
|     intptr offset= ptr - buffer;
 | |
|     DBUG_ASSERT(offset <= UINT_MAX16);
 | |
|     length= translog_get_total_chunk_length(buffer, (uint16)offset);
 | |
|   }
 | |
|   printf("      Length %u\n", length);
 | |
|   ptr+= length;
 | |
|   return ptr;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Dump information about page with data.
 | |
| */
 | |
| 
 | |
| static void dump_datapage(uchar *buffer, File handler)
 | |
| {
 | |
|   uchar *ptr;
 | |
|   ulong offset;
 | |
|   uint32 page, file;
 | |
|   uint header_len;
 | |
|   printf("  Page: %ld  File number: %ld\n",
 | |
|          (ulong) (page= uint3korr(buffer)),
 | |
|          (ulong) (file= uint3korr(buffer + 3)));
 | |
|   if (page == 0)
 | |
|     printf("    WARNING: page == 0!!!\n");
 | |
|   if (file == 0)
 | |
|     printf("    WARNING: file == 0!!!\n");
 | |
|   offset= page * TRANSLOG_PAGE_SIZE;
 | |
|   printf("  Flags (0x%x):\n", (uint) buffer[TRANSLOG_PAGE_FLAGS]);
 | |
|   if (buffer[TRANSLOG_PAGE_FLAGS])
 | |
|   {
 | |
|     if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_PAGE_CRC)
 | |
|       printf("    Page CRC\n");
 | |
|     if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION)
 | |
|       printf("    Sector protection\n");
 | |
|     if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_RECORD_CRC)
 | |
|       printf("    Record CRC (WARNING: not yet implemented!!!)\n");
 | |
|     if (buffer[TRANSLOG_PAGE_FLAGS] & ~(TRANSLOG_PAGE_CRC |
 | |
|                                         TRANSLOG_SECTOR_PROTECTION |
 | |
|                                         TRANSLOG_RECORD_CRC))
 | |
|     {
 | |
|       printf("    WARNING: unknown flags (stop interpretation)!!!\n");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|     printf("    No flags\n");
 | |
|   printf("  Page header length: %u\n",
 | |
|          (header_len= page_overhead[buffer[TRANSLOG_PAGE_FLAGS]]));
 | |
|   if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_RECORD_CRC)
 | |
|   {
 | |
|     uint32 crc= uint4korr(buffer + TRANSLOG_PAGE_FLAGS + 1);
 | |
|     uint32 ccrc;
 | |
|     printf ("  Page CRC 0x%04lx\n", (ulong) crc);
 | |
|     ccrc= translog_crc(buffer + header_len, TRANSLOG_PAGE_SIZE - header_len);
 | |
|     if (crc != ccrc)
 | |
|       printf("    WARNING: calculated CRC: 0x%04lx!!!\n", (ulong) ccrc);
 | |
|   }
 | |
|   if (buffer[TRANSLOG_PAGE_FLAGS] & TRANSLOG_SECTOR_PROTECTION)
 | |
|   {
 | |
|     TRANSLOG_FILE tfile;
 | |
|     {
 | |
|       uchar *table= buffer + header_len -
 | |
|         TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE;
 | |
|       uint i;
 | |
|       printf("    Sector protection current value: 0x%02x\n", (uint) table[0]);
 | |
|       for (i= 1; i < TRANSLOG_PAGE_SIZE / DISK_DRIVE_SECTOR_SIZE; i++)
 | |
|       {
 | |
|          printf("    Sector protection in sector: 0x%02x  saved value 0x%02x\n",
 | |
|                 (uint)buffer[i * DISK_DRIVE_SECTOR_SIZE],
 | |
|                 (uint)table[i]);
 | |
|       }
 | |
|     }
 | |
|     tfile.number= file;
 | |
|     bzero(&tfile.handler, sizeof(tfile.handler));
 | |
|     tfile.handler.file= handler;
 | |
|     tfile.was_recovered= 0;
 | |
|     tfile.is_sync= 1;
 | |
|     if (translog_check_sector_protection(buffer, &tfile))
 | |
|       printf("    WARNING: sector protection found problems!!!\n");
 | |
|   }
 | |
|   ptr= buffer + header_len;
 | |
|   while (ptr && ptr < buffer + TRANSLOG_PAGE_SIZE)
 | |
|   {
 | |
|     printf("  Chunk %d %lld:\n",
 | |
|            file,((longlong) (ptr - buffer)+ offset));
 | |
|     ptr= dump_chunk(buffer, ptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Dump information about page.
 | |
| */
 | |
| 
 | |
| void dump_page(uchar *buffer, File handler)
 | |
| {
 | |
|   if (strncmp((char*)maria_trans_file_magic, (char*)buffer,
 | |
|               sizeof(maria_trans_file_magic)) == 0)
 | |
|   {
 | |
|     dump_header_page(buffer);
 | |
|     return;
 | |
|   }
 | |
|   dump_datapage(buffer, handler);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Handle backup calls
 | |
| */
 | |
| 
 | |
| void translog_disable_purge()
 | |
| {
 | |
|   mysql_mutex_lock(&log_descriptor.purger_lock);
 | |
|   log_purge_disabled++;
 | |
|   mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
| }
 | |
| 
 | |
| void translog_enable_purge()
 | |
| {
 | |
|   mysql_mutex_lock(&log_descriptor.purger_lock);
 | |
|   log_purge_disabled--;
 | |
|   mysql_mutex_unlock(&log_descriptor.purger_lock);
 | |
| }
 |