/** Is encryption enabled/disabled */
ulong srv_encrypt_tables;
/** Version counter for innodb_encrypt_tables changes. Protectedbyfil_crypt_threads_mutex. Incrementedeachtimeinnodb_encrypt_tablesor innodb_encryption_rotate_key_ageismodifiedtosignal
encryption threads to restart iteration */ static uint32_t fil_crypt_settings_version;
/** No of key rotation threads requested */
uint srv_n_fil_crypt_threads;
/** No of key rotation threads started */
uint srv_n_fil_crypt_threads_started;
/** At this age or older a space/page will be rotated */
uint srv_fil_crypt_rotate_key_age;
/** Whether the encryption plugin does key rotation */
Atomic_relaxed<bool> srv_encrypt_rotate;
/** Condition variable for srv_n_fil_crypt_threads_started */ static pthread_cond_t fil_crypt_cond;
/** Condition variable to to signal the key rotation threads */ static pthread_cond_t fil_crypt_threads_cond;
/** Condition variable for interrupting sleeptime_ms sleep at the end
of fil_crypt_rotate_page() */ static pthread_cond_t fil_crypt_throttle_sleep_cond;
/** Condition variable for encryption threads waiting for IOPSallocation.ThisreplacedtheIOPSallocationwait functionalitypreviouslyhandledbyfil_crypt_threads_cond, preventingspuriouswakeupsofthreadswaitingforwork. SignaledwhenIOPSarereleasedviafil_crypt_release_iops(), releasedviafil_crypt_realloc_iops(),orsrv_n_fil_crypt_iops
is increased. */ static pthread_cond_t fil_crypt_iops_cond;
/** Mutex for key rotation threads. Acquired before fil_system.mutex! */ static mysql_mutex_t fil_crypt_threads_mutex;
/** Variable ensuring only 1 thread at time does initial conversion */ staticbool fil_crypt_start_converting;
/** Variables for throttling */
uint srv_n_fil_crypt_iops; // 10ms per iop static constexpr uint srv_alloc_time = 3; // allocate iops for 3s at a time static uint n_fil_crypt_iops_allocated;
/** Wake up the encryption threads */ void fil_crypt_threads_signal(bool broadcast)
{
mysql_mutex_lock(&fil_crypt_threads_mutex); if (broadcast)
pthread_cond_broadcast(&fil_crypt_threads_cond); else
pthread_cond_signal(&fil_crypt_threads_cond);
mysql_mutex_unlock(&fil_crypt_threads_mutex);
}
/*********************************************************************** Checkifakeyneedsrotationgivenakey_state @param[in]crypt_dataEncryptioninformation @param[in]key_versionCurrentkeyversion @param[in]latest_key_versionLatestkeyversion @param[in]rotate_key_agewhentorotate
@return true if key needs rotation, false if not */ staticbool
fil_crypt_needs_rotation( const fil_space_crypt_t* crypt_data,
uint key_version,
uint latest_key_version,
uint rotate_key_age)
MY_ATTRIBUTE((warn_unused_result));
/** Getlatestkeyversionfromencryptionplugin.
@return key version or ENCRYPTION_KEY_VERSION_INVALID */
uint
fil_space_crypt_t::key_get_latest_version(void)
{
uint key_version = key_found;
if (is_key_found()) {
key_version = encryption_key_get_latest_version(key_id); /* InnoDB does dirty read of srv_fil_crypt_rotate_key_age. Itdoesn'tmatterbecausesrv_encrypt_rotate
can be set to true only once */ if (!srv_encrypt_rotate
&& key_version > srv_fil_crypt_rotate_key_age) {
srv_encrypt_rotate = true;
}
crypt_data = fil_space_create_crypt_data(encryption, key_id); /* We need to overwrite these as above function will initialize
members */
crypt_data->type = type;
crypt_data->min_key_version = min_key_version;
memcpy(crypt_data->iv, page + offset + MAGIC_SZ + 2, iv_length);
return crypt_data;
}
/****************************************************************** Freeacryptdataobject
@param[in,out] crypt_data crypt data to be freed */ void fil_space_destroy_crypt_data(fil_space_crypt_t **crypt_data)
{ if (crypt_data != NULL && (*crypt_data) != NULL) {
fil_space_crypt_t* c; if (UNIV_LIKELY(fil_crypt_threads_inited)) {
mysql_mutex_lock(&fil_crypt_threads_mutex);
c = *crypt_data;
*crypt_data = NULL;
mysql_mutex_unlock(&fil_crypt_threads_mutex);
} else {
ut_ad(recv_sys.rpo || !srv_was_started);
c = *crypt_data;
*crypt_data = NULL;
} if (c) {
c->~fil_space_crypt_t();
ut_free(c);
}
}
}
b += MAGIC_SZ;
byte* const start = b;
*b++ = static_cast<byte>(type);
compile_time_assert(sizeof iv == MY_AES_BLOCK_SIZE);
compile_time_assert(sizeof iv == CRYPT_SCHEME_1_IV_LEN);
*b++ = sizeof iv;
memcpy(b, iv, sizeof iv);
b += sizeof iv;
mach_write_to_4(b, min_key_version);
b += 4;
mach_write_to_4(b, key_id);
b += 4;
*b++ = byte(encryption);
ut_ad(b - start == 11 + MY_AES_BLOCK_SIZE); /* We must log also any unchanged bytes, because recovery will
invoke fil_crypt_parse() based on this log record. */
mtr->memcpy(*block, offset + MAGIC_SZ, b - start);
}
/* For compressed tables we do not store the FIL header because thewholepageisnotstoredtothedisk.Incompressedtablesonly theFILheader+compressed(andnowencrypted)payloadalligned
to sector boundary is written. */ if (!page_compressed) { /* FIL page trailer is also not encrypted */
static_assert(FIL_PAGE_DATA_END == 8, "alignment");
memcpy_aligned<8>(dst_frame + size - FIL_PAGE_DATA_END,
src_frame + size - FIL_PAGE_DATA_END, 8);
} else { /* Clean up rest of buffer */
memset(dst_frame+header_len+srclen, 0,
size - (header_len + srclen));
}
/* store the post-encryption checksum after the key-version */
mach_write_to_4(dst_frame + FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION + 4,
zip_size
? page_zip_calc_checksum(dst_frame, zip_size,
SRV_CHECKSUM_ALGORITHM_CRC32)
: buf_calc_page_crc32(dst_frame));
/** Check whether these page types are allowed to encrypt. @param[in]spacetablespaceobject @param[in]src_framesourcepage
@return true if it is valid page type */ staticbool fil_space_encrypt_valid_page_type( const fil_space_t* space, const byte* src_frame)
{ switch (fil_page_get_type(src_frame)) { case FIL_PAGE_RTREE: return space->full_crc32(); case FIL_PAGE_TYPE_FSP_HDR: case FIL_PAGE_TYPE_XDES: returnfalse;
}
/* Copy only checksum part in the trailer */
memcpy(tmp_frame + srv_page_size - FIL_PAGE_FCRC32_CHECKSUM,
src_frame + srv_page_size - FIL_PAGE_FCRC32_CHECKSUM,
FIL_PAGE_FCRC32_CHECKSUM);
/* For compressed tables we do not store the FIL header because thewholepageisnotstoredtothedisk.Incompressedtablesonly theFILheader+compressed(andnowencrypted)payloadalligned
to sector boundary is written. */ if (!page_compressed) { /* Copy FIL trailer */
memcpy(tmp_frame + physical_size - FIL_PAGE_DATA_END,
src_frame + physical_size - FIL_PAGE_DATA_END,
FIL_PAGE_DATA_END);
}
/* Copy the decrypted page back to page buffer, not
really any other options. */ returnstatic_cast<byte*>(memcpy(src_frame, tmp_frame, physical_size));
}
/*********************************************************************** Checkifakeyneedsrotationgivenakey_state @param[in]crypt_dataEncryptioninformation @param[in]key_versionCurrentkeyversion @param[in]latest_key_versionLatestkeyversion @param[in]rotate_key_agewhentorotate
@return true if key needs rotation, false if not */ staticbool
fil_crypt_needs_rotation( const fil_space_crypt_t* crypt_data,
uint key_version,
uint latest_key_version,
uint rotate_key_age)
{ if (key_version == ENCRYPTION_KEY_VERSION_INVALID) { returnfalse;
}
if (key_version == 0 && latest_key_version != 0) { /* this is rotation unencrypted => encrypted
* ignore rotate_key_age */ returntrue;
}
if (latest_key_version == 0 && key_version != 0) { if (crypt_data->encryption == FIL_ENCRYPTION_DEFAULT) { /* this is rotation encrypted => unencrypted */ returntrue;
} returnfalse;
}
if (crypt_data->encryption == FIL_ENCRYPTION_DEFAULT
&& crypt_data->type == CRYPT_SCHEME_1
&& !srv_encrypt_tables) { /* This is rotation encrypted => unencrypted */ returntrue;
}
if (rotate_key_age == 0) { returnfalse;
}
/* this is rotation encrypted => encrypted,
* only reencrypt if key is sufficiently old */ if (key_version + rotate_key_age < latest_key_version) { returntrue;
}
returnfalse;
}
/** Read page 0 and possible crypt data from there.
@param[in,out] space Tablespace */ staticinlinevoid fil_crypt_read_crypt_data(fil_space_t *space)
{ if (space->crypt_data || space->size || !space->get_size()) /* The encryption metadata has already been read, or the tablespaceisnotencryptedandthefilehasbeenopenedalready, orthefilecannotbeaccessed,likelyduetoaconcurrentDROP (possiblyaspartofTRUNCATEorALTERTABLE).
FIXME:Thefilecanbecomeunaccessibleanytimeafterthischeck! Weshouldreallyremovethisfunctionandinsteadmakecrypt_data
an integral part of fil_space_t. */ return;
/** Start encrypting a space @param[in,out]spaceTablespace
@return true if a recheck of tablespace is needed by encryption thread. */ staticbool fil_crypt_start_encrypting_space(fil_space_t* space)
{
mysql_mutex_lock(&fil_crypt_threads_mutex);
/* If space is not encrypted and encryption is not enabled, then
do not continue encrypting the space. */ if (!crypt_data && !srv_encrypt_tables) {
func_exit:
mysql_mutex_unlock(&fil_crypt_threads_mutex); returnfalse;
}
/* NOTE: we need to write and flush page 0 before publishing *thecryptdata.Thissothatafterrestartthereisno *riskoffindingencryptedpageswithouthaving
* crypt data in page 0 */
/** State of a rotation thread */ struct rotate_thread_t { explicit rotate_thread_t(uint no) :
settings_version(fil_crypt_settings_version), thread_no(no){}
bool first = true; /*!< is position before first space */
/** Initial wait timeout in nanoseconds, corresponding to
timed_wait_count=0 (5 seconds). */ static constexpr ulonglong SLEEP_TIMEOUT_NSEC{5 * 1000000000ULL};
/** Maximum wait timeout in nanoseconds (60 seconds). */ static constexpr ulonglong MAX_SLEEP_TIMEOUT_NSEC{60 * 1000000000ULL};
/** Number of consecutive timed waits that resulted in timeout (no signal). Usedforexponentialbackoffwheniteratingfil_system.space_list:after 5timeouts(~135stotal:5+10+20+40+60),switchestoindefinitewaitto avoidwastingCPUwhenspacesremainunavailable.Resetto0whenwoken
by signal or when work is found. */
uint8_t timed_wait_count= 0;
/** Config version when thread started current iteration. Usedtodetectinnodb_encrypt_tableschangesduringiteration
and restart from beginning to ensure complete encryption coverage. */
uint32_t settings_version;
uint thread_no;
uint32_t offset = 0; /*!< current page number */
uint min_key_version_found = 0; /*!< min key version found but not rotated */
uint estimated_max_iops = 20;/*!< estimation of max iops */
uint allocated_iops = 0; /*!< allocated iops */
space_list_t::iterator space
= fil_system.space_list.end();/*!< current space or .end() */
ulint batch = 0; /*!< #pages to rotate */
lsn_t end_lsn = 0; /*!< max lsn when rotating this space */
ulint cnt_waited = 0; /*!< #times waited during this slot */
uintmax_t sum_waited_us = 0; /*!< wait time during this slot */ /** Per-thread rotation counters, periodically folded into
the global crypt_stat by fil_crypt_update_total_stat() */
fil_crypt_stat_t crypt_stat;
/** Check if innodb_encrypt_tables config has changed.
@return true if config changed, requiring iteration restart */ bool settings_changed() const noexcept {
mysql_mutex_assert_owner(&fil_crypt_threads_mutex); return settings_version != fil_crypt_settings_version;
}
/** @return whether this thread should terminate */ bool should_shutdown() const {
mysql_mutex_assert_owner(&fil_crypt_threads_mutex); switch (srv_shutdown_state) { case SRV_SHUTDOWN_NONE: return thread_no >= srv_n_fil_crypt_threads; case SRV_SHUTDOWN_EXIT_THREADS: /* srv_init_abort() must have been invoked */ case SRV_SHUTDOWN_CLEANUP: case SRV_SHUTDOWN_INITIATED: returntrue; case SRV_SHUTDOWN_LAST_PHASE: break;
}
ut_ad(0); returntrue;
}
/** Wait for encryption work to become available. whenspace==fil_system.space_list.end()(endofiteration,or allspacestemporarilyunacquirableduetoSTOPPING/CLOSING): usetimedwaitwithexponentialbackoff(5/10/20/40/60s,cappedat60s) soDDLcompletionsarepickedupwithoutexplicitsignal. After5consecutivetimeouts(~135s),switchtoindefinitewait toavoidCPUwaste.
Otherwise:indefinitewaituntilsignaled Timeoutcounterresetsto0whenwokenbysignal
(config change, new tablespace) or when work is found. */ void wait_for_work() noexcept
{ if (space == fil_system.space_list.end())
{ /* Keep exponential backoff but never switch to indefinite wait. Thispreservesthe"cheapmask"formissingsignalswhenspaces
become acquirable */ struct timespec abstime; /* Unary '+' forces a prvalue; without it std::min would
ODR-use the static constexpr member (C++11). */
set_timespec_nsec(abstime,
std::min(SLEEP_TIMEOUT_NSEC << timed_wait_count,
+MAX_SLEEP_TIMEOUT_NSEC)); int ret= my_cond_timedwait(&fil_crypt_threads_cond,
&fil_crypt_threads_mutex.m_mutex, &abstime); if (ret == ETIMEDOUT)
timed_wait_count= uint8_t(timed_wait_count + (timed_wait_count < 5)); else timed_wait_count= 0;
} else
{ /* Wait indefinitely when no work in default_encrypt_list. temp_spaceisasentinelvaluemeaning"default_encrypt_list exhausted".Indefinitewaitissafeherebecausenewwork
will be explicitly signaled. */
ut_ad(space == space_list_t::iterator(fil_system.temp_space));
my_cond_wait(&fil_crypt_threads_cond,
&fil_crypt_threads_mutex.m_mutex);
}
}
};
/** Avoid the removal of the tablespace from default_encrypt_listonlywhen 1)Anotheractiveencryptionthreadworkingontablespace 2)Eligiblefortablespacekeyrotation 3)Tablespaceisinflushingphase @returntrueiftablespaceshouldberemovedfrom
default encrypt */ staticbool fil_crypt_must_remove(const fil_space_t &space)
{
ut_ad(!space.is_temporary());
ut_ad(!space.is_being_imported());
fil_space_crypt_t *crypt_data = space.crypt_data;
mysql_mutex_assert_owner(&fil_system.mutex); const ulong encrypt_tables= srv_encrypt_tables; if (!crypt_data) return !encrypt_tables; if (!crypt_data->is_key_found()) returntrue;
/* If used key_id is not found from encryption plugin we can't
continue to rotate the tablespace */ if (!crypt_data->is_key_found()) { returnfalse;
}
bool need_key_rotation = false;
mysql_mutex_lock(&crypt_data->mutex);
do { /* prevent threads from starting to rotate space */ if (crypt_data->rotate_state.starting) { /* recheck this space later */
*recheck = true; break;
}
/* prevent threads from starting to rotate space */ if (space->is_stopping()) { break;
}
if (crypt_data->rotate_state.flushing) { break;
}
/* No need to rotate space if encryption is disabled */ if (crypt_data->not_encrypted()) { break;
}
if (crypt_data->key_id != key_state->key_id) {
key_state->key_id= crypt_data->key_id;
fil_crypt_get_key_state(key_state, crypt_data);
}
need_key_rotation = fil_crypt_needs_rotation(
crypt_data,
crypt_data->min_key_version,
key_state->key_version,
key_state->rotate_key_age);
} while (0);
/** Reallocateiopstothreadwhenprocessingatablespace @param[in,out]stateRotationstate
@return whether the thread should continue running */ staticbool fil_crypt_realloc_iops(rotate_thread_t *state)
{
ut_a(state->allocated_iops > 0);
if (10 * state->cnt_waited > state->batch) { /* if we waited more than 10% re-estimate max_iops */
ulint avg_wait_time_us =
ulint(state->sum_waited_us / state->cnt_waited);
if (avg_wait_time_us == 0) {
avg_wait_time_us = 1; // prevent division by zero
}
DBUG_PRINT("ib_crypt",
("thr_no: %u - update estimated_max_iops from %u to "
ULINTPF ".",
state->thread_no,
state->estimated_max_iops, 1000000 / avg_wait_time_us));
/** Acquire a tablespace reference.
@return whether a tablespace reference was successfully acquired */ inlinebool fil_space_t::acquire_if_not_stopped()
{
mysql_mutex_assert_owner(&fil_system.mutex); const uint32_t n= acquire_low(); if (UNIV_LIKELY(!(n & (STOPPING | CLOSING)))) returntrue; if (UNIV_UNLIKELY(n & STOPPING)) returnfalse; return UNIV_LIKELY(!(n & CLOSING)) || prepare_acquired();
}
bool fil_crypt_must_default_encrypt()
{ /* prevents a race condition with fil_crypt_set_rotate_key_age() */
mysql_mutex_assert_owner(&fil_system.mutex); return !srv_fil_crypt_rotate_key_age || !srv_encrypt_rotate;
}
/** Return the next tablespace from default_encrypt_tables list. @paramspaceprevioustablespace(nullptrtostartfromthestart) @paramrecheckwhethertheremovalconditionneedstoberecheckedafter theencryptionparameterswerechanged @paramencryptexpectedstateofinnodb_encrypt_tables @returnthenexttablespacetoprocess(n_pending_opsincremented) @retvalfil_system.temp_spaceifthereisnoworktodo
@retval nullptr upon reaching the end of the iteration */ inline fil_space_t *fil_system_t::default_encrypt_next(fil_space_t *space, bool recheck, bool encrypt) noexcept
{
mysql_mutex_assert_owner(&mutex);
auto it= space && space->is_in_default_encrypt
? sized_ilist<fil_space_t, default_encrypt_tag_t>::iterator(space)
: default_encrypt_tables.begin(); constauto end= default_encrypt_tables.end();
if (space)
{ constbool released= !space->release();
if (space->is_in_default_encrypt)
{ while (++it != end &&
(!UT_LIST_GET_LEN(it->chain) || it->is_stopping()));
/* If one of the encryption threads already started theencryptionofthetablethendon'tremovethe unencryptedspacesfromdefaultencryptlist.
Ifthereisachangeininnodb_encrypt_tablesvariables valuethendon'tremovethelastprocessedtablespace
from the default encrypt list. */ if (released && !recheck && fil_crypt_must_remove(*space))
{
ut_a(!default_encrypt_tables.empty());
default_encrypt_tables.remove(*space);
space->is_in_default_encrypt= false;
}
}
} elsewhile (it != end &&
(!UT_LIST_GET_LEN(it->chain) || it->is_stopping()))
{ /* Find the next suitable default encrypt table if beginningofdefault_encrypt_tableslisthasbeenscheduled
to be deleted */
it++;
}
if (it == end) return temp_space;
do
{
space= &*it; if (space->acquire_if_not_stopped()) return space; if (++it == end) return nullptr;
} while (!UT_LIST_GET_LEN(it->chain) || it->is_stopping());
return nullptr;
}
/** Get the next tablespace for encryption key rotation @paramspaceCurrentspaceiterator,or.end()tostartfrombeginning @paramrecheckSkipremovingprocessedspacesfromdefault_encrypt_list @paramencryptWhetherencryptionisenabled @returniteratortonextspacetoprocess Returnvaluesdependonwhichlistisbeingiterated:
while (state->space != fil_system.space_list.end()) { if (state->space
== space_list_t::iterator(fil_system.temp_space)) { return !state->should_shutdown();
}
if (state->should_shutdown()) {
state->space->release();
state->space = fil_system.space_list.end(); returnfalse;
}
mysql_mutex_unlock(&fil_crypt_threads_mutex); /* If there is no crypt data and we have not yet read page0forthistablespace,weneedtoreaditbefore
we can continue. */ if (!state->space->crypt_data) {
fil_crypt_read_crypt_data(&*state->space);
}
if (fil_crypt_space_needs_rotation(state, key_state, recheck)) {
ut_ad(key_state->key_id); /* init state->min_key_version_found before
* starting on a space */
state->min_key_version_found = key_state->key_version;
mysql_mutex_lock(&fil_crypt_threads_mutex); returntrue;
}
if (crypt_data->rotate_state.active_threads == 0) { /* only first thread needs to init */
crypt_data->rotate_state.next_offset = 1; // skip page 0 /* no need to rotate beyond current max
* if space extends, it will be encrypted with newer version */ /* FIXME: max_offset could be removed and instead
space->size consulted.*/
crypt_data->rotate_state.max_offset = state->space->size;
crypt_data->rotate_state.end_lsn = 0;
crypt_data->rotate_state.min_key_version_found =
key_state->key_version;
crypt_data->rotate_state.aborted = false;
crypt_data->rotate_state.start_time = time(0);
if (crypt_data->type == CRYPT_SCHEME_UNENCRYPTED &&
crypt_data->is_encrypted() &&
key_state->key_version != 0) { /* this is rotation unencrypted => encrypted */
crypt_data->type = CRYPT_SCHEME_1;
}
}
/* count active threads in space */
crypt_data->rotate_state.active_threads++;
/* Initialize thread local state */
state->end_lsn = crypt_data->rotate_state.end_lsn;
state->min_key_version_found =
crypt_data->rotate_state.min_key_version_found;
mysql_mutex_unlock(&crypt_data->mutex);
}
/*********************************************************************** Searchforbatchofpagesneedingrotation @param[in]key_stateKeystate @param[in,out]stateRotationstate
@return true if page needing key rotation found, false if not found */ static bool
fil_crypt_find_page_to_rotate( const key_state_t* key_state,
rotate_thread_t* state)
{
ulint batch = srv_alloc_time * state->allocated_iops;
/* Before reading from tablespace we need to make sure that
the tablespace is not about to be dropped. */ if (space->is_stopping()) { return NULL;
}
if (offset % (zip_size ? zip_size : srv_page_size)) { /* Try to acquire X-lock for fseg_page_is_allocated()check.Ifitfails,
back off and retry to avoid deadlock. */ if (!space->x_lock_try()) { returnreinterpret_cast<buf_block_t*>(-1);
}
constbool skip = space->is_stopping() ||
DB_SUCCESS_LOCKED_REC
!= fseg_page_is_allocated(mtr, space, offset);
space->x_unlock(); if (skip) { return nullptr;
} /* mtr continues to hold a latch on the allocation
bitmap page. */
}
if (avg_wait_time_us < alloc_wait_us) { /* we reading faster than we allocated */
add_sleeptime_ms = (alloc_wait_us - avg_wait_time_us) / 1000;
} else { /* if page load time is longer than we want, skip sleeping */
}
/* In fil_crypt_thread where key rotation is done we have acquiredspaceandcheckedthatthisspaceisnotyet markedtobedropped.Similarly,infil_crypt_find_page_to_rotate().
Check here also to give DROP TABLE or similar a change. */ if (space->is_stopping()) { return;
}
if (space_id == TRX_SYS_SPACE && offset == TRX_SYS_PAGE_NO) { /* don't encrypt this as it contains address to dblwr buffer */ return;
}
uint32_t retry_count= 0;
retry_page:
mtr_t mtr{nullptr};
mtr.start();
buf_block_t* block = fil_crypt_get_page_throttle(state, offset, &mtr,
&sleeptime_ms); /* mtr may contain bitmap page latch in memo, but should have
no log records since we only read, never modified pages */
ut_ad(mtr.has_no_log()); if (block == reinterpret_cast<buf_block_t*>(-1)) {
mtr.commit(); if (space->is_stopping()) { return;
}
retry_count++;
/* After multiple retries, temporarily release IOPS to allow otherencryptionthreadstomakeprogress,especiallythose workingontablespacesthatarebeingdropped.Thisprevents deadlockwhereonethreadholdsIOPSwhilestuckonapage,
blocking other threads from checking is_stopping(). */ if (retry_count > 10) {
/* Try to reacquire IOPS without waiting */
mysql_mutex_lock(&fil_crypt_threads_mutex); bool alloc_iops = fil_crypt_alloc_iops(state, true);
mysql_mutex_unlock(&fil_crypt_threads_mutex); if (!alloc_iops) { /* IOPS not immediately available. This is expected underhighload.Markfirst=truetoreleasecurrent
space and allow the thread to find new work or exit. */
state->first = true; return;
}
retry_count = 0;
}
if (block->page.oldest_modification() > 1) { /* Do not unnecessarily touch pages that are
already dirty. */
} elseif (space->is_stopping()) { /* The tablespace is closing (in DROP TABLE or
TRUNCATE TABLE or similar): avoid further access */
} elseif (!kv && !*reinterpret_cast<uint16_t*>
(&frame[FIL_PAGE_TYPE])) { /* It looks like this page is not allocated.Becausekeyrotationisaccessing pagesinapatternthatisunlikethenormal B-treeandundologaccesspattern,wecannot invokefseg_page_is_allocated()here,becausethat couldresultinadeadlock.Ifweinvoked fseg_page_is_allocated()andreleasedthe tablespacelatchbeforeacquiringblock->lock, thenthefseg_page_is_allocated()information
could be stale already. */
/* If the data file was originally created beforeMariaDB10.0orMySQL5.6,some allocateddatapagescouldcarry0in FIL_PAGE_TYPE.TheFIL_PAGE_TYPEonthose pageswillbeupdatedin buf_flush_init_for_writing()whenthepage ismodifiedthenexttime.
if (modified) { /* if we modified page, we take lsn from mtr */
ut_a(end_lsn > state->end_lsn);
ut_a(end_lsn > block_lsn);
state->end_lsn = end_lsn;
} else { /* if we did not modify page, check for max lsn */ if (block_lsn > state->end_lsn) {
state->end_lsn = block_lsn;
}
}
} else {
mtr.commit();
}
/*********************************************************************** Rotateabatchofpages @param[in,out]key_stateKeystate
@param[in,out] state Rotation state */ static void
fil_crypt_rotate_pages( const key_state_t* key_state,
rotate_thread_t* state)
{ const uint32_t space_id = state->space->id;
uint32_t end = std::min(state->offset + uint32_t(state->batch),
state->space->free_limit);
ut_ad(state->space->referenced());
for (; state->offset < end; state->offset++) {
/* we can't rotate pages in dblwr buffer as *it'snotpossibletoreadthoseduetolotsofasserts *inbufferpool. * *Howeversincetheseareonly(short-lived)copiesof *realpages,theywillbeupdatedanywaywhenthe *realpageisupdated
*/ if (buf_dblwr.is_inside(page_id_t(space_id, state->offset))) { continue;
}
/* If space is marked as stopping, stop rotating
pages. */ if (state->space->is_stopping()) { break;
}
fil_crypt_rotate_page(key_state, state); /* If fil_crypt_rotate_page() set first=true due to IOPSallocationfailureorshutdown,breakoutto avoidmarkingpagesasencryptedwhenthey'renot. Markthepassasabortedsothat fil_crypt_complete_rotate_space()doesnotcommit min_key_version_foundwhilepagesinthisbatch remainunvisited.Thespacewillberetriedin
the next iteration. */ if (state->first) {
fil_space_crypt_t *crypt_data =
state->space->crypt_data;
mysql_mutex_lock(&crypt_data->mutex);
crypt_data->rotate_state.aborted = true;
mysql_mutex_unlock(&crypt_data->mutex); break;
}
}
}
/* we should flush space if we're last thread AND theiterationisdoneANDnothreadbailedoutof
its claimed batch during this pass. */ if (done && !(crypt_data->rotate_state.active_threads
| ulint{crypt_data->rotate_state.aborted})) { /* we're the last active thread */
crypt_data->rotate_state.flushing = true;
crypt_data->min_key_version =
crypt_data->rotate_state.min_key_version_found;
mysql_mutex_unlock(&crypt_data->mutex);
fil_crypt_flush_space(state);
/** A thread which monitors global key state and rotates tablespaces
accordingly */ staticvoid fil_crypt_thread()
{
my_thread_init(); #ifdef UNIV_PFS_THREAD
pfs_register_thread(page_encrypt_thread_key); #endif/* UNIV_PFS_THREAD */
mysql_mutex_lock(&fil_crypt_threads_mutex);
rotate_thread_t thr(srv_n_fil_crypt_threads_started++);
pthread_cond_broadcast(&fil_crypt_cond);
if (!thr.should_shutdown()) { /* if we find a tablespace that is starting, skip over it
and recheck it later */ bool recheck = false;
wait_for_work: if (!recheck && !thr.should_shutdown()) {
thr.wait_for_work();
}
restart_iteration:
recheck = false;
thr.first = true; // restart from first tablespace
thr.settings_version = fil_crypt_settings_version;
key_state_t new_state;
/* iterate all spaces searching for those needing rotation */ while (fil_crypt_find_space_to_rotate(&new_state, &thr,
&recheck)) { /* Check if innodb_encrypt_tables or innodb_encryption_rotate_key_agechangedduring
iteration. If changed, restart immediately */ if (thr.settings_changed()) { goto restart_iteration;
}
if (thr.space == fil_system.space_list.end()) { /* Reaching .end() means either all spaces are encrypted,orsomeweretemporarilyunacquirable (CLOSING/STOPPINGflag,DDLinprogress).Either way,fallthroughtowait_for_work()whichuses timedbackoff(5/10/20/40/60s)toperiodically
recheck for spaces that become available. */ goto wait_for_work;
}
if (thr.space ==
space_list_t::iterator(fil_system.temp_space)) { /* temp_space : No work exists, don't
wake others */ goto wait_for_work;
}
if (thr.should_shutdown()) {
thr.space->release();
thr.space = fil_system.space_list.end(); goto func_exit;
}
/* Try to allocate IOPS without waiting first */ if (!fil_crypt_alloc_iops(&thr, true)) { /* IOPS not immediately available. Release space
reference before waiting to avoid blocking DROP TABLE. */
uint32_t space_id = thr.space->id;
thr.space->release();
thr.space = fil_system.space_list.end();
/* Now wait for IOPS */ if (!fil_crypt_alloc_iops(&thr, false)) { /* Failed to allocate IOPS (shutdown, thread reduction,orIOPSlimitis0).Retrythis
space after iteration completes. */
recheck = true; continue;
}
/* Reacquire the space after IOPS allocation */
mysql_mutex_unlock(&fil_crypt_threads_mutex);
fil_space_t *space = fil_space_t::get_for_write(space_id); if (!space || space->is_being_imported()) { /* Space was dropped or is being
imported while allocating IOPS */ if (space) {
space->release();
}
/* iterate all pages (cooperativly with other threads) */ while (fil_crypt_find_page_to_rotate(&new_state, &thr)) {
/* If space is marked as stopping, release
space and stop rotation. */ if (thr.space->is_stopping()) {
fil_crypt_complete_rotate_space(&thr);
thr.space->release();
thr.space = fil_system.space_list.end(); break;
}
fil_crypt_rotate_pages(&new_state, &thr); /* If fil_crypt_rotate_pages() set first=true duetoIOPSallocationfailure,skiprealloc sinceIOPSwerealreadyreleasedand
allocated_iops = 0 */ if (thr.first) { break;
}
/* realloc iops */ if (!fil_crypt_realloc_iops(&thr)) { break;
}
}
/********************************************************************* Adjustthreadcountforkeyrotation
@param[in] enw_cnt Number of threads to be used */ void fil_crypt_set_thread_cnt(const uint new_cnt)
{
ut_ad(!srv_read_only_mode || recv_sys.rpo);
if (recv_sys.rpo) return;
if (!fil_crypt_threads_inited) { if (srv_shutdown_state != SRV_SHUTDOWN_NONE) return;
fil_crypt_threads_init();
}
/********************************************************************* Adjustmaxkeyage
@param[in] val New max key age */ void fil_crypt_set_rotate_key_age(uint val)
{
mysql_mutex_lock(&fil_crypt_threads_mutex);
mysql_mutex_lock(&fil_system.mutex);
srv_fil_crypt_rotate_key_age= val; if (val == 0)
fil_crypt_default_encrypt_tables_fill();
mysql_mutex_unlock(&fil_system.mutex);
/* Increment version to signal threads to restart iteration */
fil_crypt_settings_version++;
/********************************************************************* Adjustrotationiops
@param[in] val New max roation iops */ void fil_crypt_set_rotation_iops(uint val)
{
mysql_mutex_lock(&fil_crypt_threads_mutex);
srv_n_fil_crypt_iops= val;
pthread_cond_broadcast(&fil_crypt_iops_cond);
mysql_mutex_unlock(&fil_crypt_threads_mutex);
}
/** Add the import tablespace to default_encrypt list ifnecessaryandsignalfil_crypt_threads
@param space imported tablespace */ void fil_crypt_add_imported_space(fil_space_t *space)
{
mysql_mutex_lock(&fil_crypt_threads_mutex);
mysql_mutex_lock(&fil_system.mutex);
if (fil_crypt_must_default_encrypt())
{
fil_system.default_encrypt_tables.push_back(*space);
space->is_in_default_encrypt= true;
}
/********************************************************************* Adjustencrypttables
@param[in] val New setting for innodb-encrypt-tables */ void fil_crypt_set_encrypt_tables(ulong val)
{ if (!fil_crypt_threads_inited) return;
if (UNIV_UNLIKELY(now >= last + 30)) {
ib::warn() << "Waited "
<< now - start
<< " seconds to drop space: "
<< space->chain.start->name << " ("
<< space->id << ") active threads "
<< crypt_data->rotate_state.active_threads
<< " flushing="
<< crypt_data->rotate_state.flushing << ".";
last = now;
}
mysql_mutex_lock(&crypt_data->mutex);
}
mysql_mutex_unlock(&crypt_data->mutex);
}
/********************************************************************* Getcryptstatusforaspace(usedbyinformation_schema) @param[in]spaceTablespace
@param[out] status Crypt status */ void
fil_space_crypt_get_status( const fil_space_t* space, struct fil_space_crypt_status_t* status)
{
memset(status, 0, sizeof(*status));
ut_ad(space->referenced());
/* If there is no crypt data and we have not yet read page0forthistablespace,weneedtoreaditbefore
we can continue. */ if (!space->crypt_data) {
fil_crypt_read_crypt_data(const_cast<fil_space_t*>(space));
}
@param[in,out]pagepageframe(checksumistemporarilymodified) @param[in]zip_sizeROW_FORMAT=COMPRESSEDpagesize,or0
@return true if page is encrypted AND OK, false otherwise */ bool fil_space_verify_crypt_checksum(const byte* page, ulint zip_size)
{ if (ENCRYPTION_KEY_NOT_ENCRYPTED == mach_read_from_4(
page + FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION)) { returnfalse;
}
/* Compressed and encrypted pages do not have checksum. Assume not corrupted.Pageverificationhappensafterdecompressionin
buf_page_t::read_complete() using buf_page_is_corrupted(). */ if (fil_page_get_type(page) == FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED) { returntrue;
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.72Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.