/* Version 2 uses the kernel space UDS index and is limited to 16 bytes */ #define UDS_ADVICE_VERSION 2 /* version byte + state byte + 64-bit little-endian PBN */ #define UDS_ADVICE_SIZE (1 + 1 + sizeof(u64))
enum hash_lock_state { /* State for locks that are not in use or are being initialized. */
VDO_HASH_LOCK_INITIALIZING,
/* This is the sequence of states typically used on the non-dedupe path. */
VDO_HASH_LOCK_QUERYING,
VDO_HASH_LOCK_WRITING,
VDO_HASH_LOCK_UPDATING,
/* The remaining states are typically used on the dedupe path in this order. */
VDO_HASH_LOCK_LOCKING,
VDO_HASH_LOCK_VERIFYING,
VDO_HASH_LOCK_DEDUPING,
VDO_HASH_LOCK_UNLOCKING,
/* This spinlock protects the state fields and the starting of dedupe requests. */
spinlock_t lock;
/* The fields in the next block are all protected by the lock */ struct vdo_completion completion; enum index_state index_state; enum index_state index_target; struct admin_state state; bool changing; bool create_flag; bool dedupe_flag; bool error_flag;
u64 reported_timeouts;
/* The number of zones */
zone_count_t zone_count; /* The hash zones themselves */ struct hash_zone zones[];
};
/* These are in milliseconds. */ unsignedint vdo_dedupe_index_timeout_interval = 5000; unsignedint vdo_dedupe_index_min_timer_interval = 100; /* Same two variables, in jiffies for easier consumption. */ static u64 vdo_dedupe_index_timeout_jiffies; static u64 vdo_dedupe_index_min_timer_jiffies;
/** *get_hash_lock_state_name()-Getthestringrepresentationofahashlockstate. *@state:Thehashlockstate. * *Return:Theshortstringrepresentingthestate
*/ staticconstchar *get_hash_lock_state_name(enum hash_lock_state state)
{ /* Catch if a state has been added without updating the name array. */
BUILD_BUG_ON((VDO_HASH_LOCK_BYPASSING + 1) != ARRAY_SIZE(LOCK_STATE_NAMES)); return (state < ARRAY_SIZE(LOCK_STATE_NAMES)) ? LOCK_STATE_NAMES[state] : "INVALID";
}
/** *assert_hash_lock_agent()-Assertthatadata_vioistheagentofitshashlock,andthatthis *isbeingcalledinthehashzone. *@data_vio:Thedata_vioexpectedtobethelockagent. *@where:Astringdescribingthefunctionmakingtheassertion.
*/ staticvoid assert_hash_lock_agent(struct data_vio *data_vio, constchar *where)
{ /* Not safe to access the agent field except from the hash zone. */
assert_data_vio_in_hash_zone(data_vio);
VDO_ASSERT_LOG_ONLY(data_vio == data_vio->hash_lock->agent, "%s must be for the hash lock agent", where);
}
/** *set_duplicate_lock()-Settheduplicatelockheldbyahashlock.Mayonlybecalledinthe *physicalzoneofthePBNlock. *@hash_lock:Thehashlocktoupdate. *@pbn_lock:ThePBNreadlocktouseastheduplicatelock.
*/ staticvoid set_duplicate_lock(struct hash_lock *hash_lock, struct pbn_lock *pbn_lock)
{
VDO_ASSERT_LOG_ONLY((hash_lock->duplicate_lock == NULL), "hash lock must not already hold a duplicate lock");
pbn_lock->holder_count += 1;
hash_lock->duplicate_lock = pbn_lock;
}
if (old_lock != NULL) {
VDO_ASSERT_LOG_ONLY(data_vio->hash_zone != NULL, "must have a hash zone when holding a hash lock");
VDO_ASSERT_LOG_ONLY(!list_empty(&data_vio->hash_lock_entry), "must be on a hash lock list when holding a hash lock");
VDO_ASSERT_LOG_ONLY(old_lock->reference_count > 0, "hash lock reference must be counted");
if ((old_lock->state != VDO_HASH_LOCK_BYPASSING) &&
(old_lock->state != VDO_HASH_LOCK_UNLOCKING)) { /* *Ifthereferencecountgoestozeroinanon-terminalstate,we'remost *likelyleakingthislock.
*/
VDO_ASSERT_LOG_ONLY(old_lock->reference_count > 1, "hash locks should only become unreferenced in a terminal state, not state %s",
get_hash_lock_state_name(old_lock->state));
}
if (lock->duplicate_lock != NULL) { /* The agent must reference the duplicate zone to launch it. */
data_vio->duplicate = lock->duplicate;
launch_data_vio_duplicate_zone_callback(data_vio, unlock_duplicate_pbn); return;
}
VDO_ASSERT_LOG_ONLY(lock->verified, "new advice should have been verified");
VDO_ASSERT_LOG_ONLY(lock->update_advice, "should only update advice if needed");
VDO_ASSERT_LOG_ONLY(lock->agent == NULL, "shouldn't have an agent in DEDUPING");
VDO_ASSERT_LOG_ONLY(!vdo_waitq_has_waiters(&lock->waiters), "shouldn't have any lock waiters in DEDUPING");
/* Just release the lock reference if other data_vios are still deduping. */ if (lock->reference_count > 1) {
exit_hash_lock(data_vio); return;
}
/* The hash lock must have an agent for all other lock states. */
lock->agent = agent; if (lock->update_advice) { /* *DEDUPING->UPDATINGtransition:Thelocationoftheduplicateblockchanged *sincetheinitialUDSquerybecauseofcompression,rollover,orbecausethe *queryagentdidn'thaveanallocation.TheUDSupdatewasdelayedincasethere *wasanotherchangeinlocation,butwithonlythisdata_viousingthehashlock, *it'stimetoupdatetheadvice.
*/
start_updating(lock, agent);
} else { /* *DEDUPING->UNLOCKINGtransition:ReleasethePBNreadlockontheduplicate *locationsothehashlockitselfcanbereleased(contingentonnonewdata_vios *arrivinginthelockbeforetheagentreturns).
*/
start_unlocking(lock, agent);
}
}
/* *Borrowandpreparealockfromthepoolsowedon'thavetodotwoint_mapaccesses *inthecommoncaseofnolockcontention.
*/
result = VDO_ASSERT(!list_empty(&zone->lock_pool), "never need to wait for a free hash lock"); if (result != VDO_SUCCESS) return result;
if (replace_lock != NULL) { /* On mismatch put the old lock back and return a severe error */
VDO_ASSERT_LOG_ONLY(lock == replace_lock, "old lock must have been in the lock map"); /* TODO: Check earlier and bail out? */
VDO_ASSERT_LOG_ONLY(replace_lock->registered, "old lock must have been marked registered");
replace_lock->registered = false;
}
if (lock == replace_lock) {
lock = new_lock;
lock->registered = true;
} else { /* There's already a lock for the hash, so we don't need the borrowed lock. */
return_hash_lock_to_pool(zone, vdo_forget(new_lock));
}
/** *launch_dedupe()-Reserveareferencecountincrementforadata_vioandlaunchitonthededupe *path. *@lock:Thehashlock. *@data_vio:Thedata_viotodeduplicateusingthehashlock. *@has_claim:trueifthedata_vioalreadyhasclaimedanincrementfromtheduplicatelock. * *Ifnoincrementsareavailable,thiswillrollovertoanewhashlockandlaunchthedata_vio *asthewritingagentforthatlock.
*/ staticvoid launch_dedupe(struct hash_lock *lock, struct data_vio *data_vio, bool has_claim)
{ if (!has_claim && !vdo_claim_pbn_lock_increment(lock->duplicate_lock)) { /* Out of increments, so must roll over to a new lock. */
fork_hash_lock(lock, data_vio); return;
}
/* Deduplicate against the lock's verified location. */
set_duplicate_location(data_vio, lock->duplicate);
data_vio->new_mapped = data_vio->duplicate;
update_metadata_for_data_vio_write(data_vio, lock->duplicate_lock);
}
/* *Wedon'ttakethedowngradedallocationlockfromtheagentunlessweactuallyneedto *deduplicateagainstit.
*/ if (lock->duplicate_lock == NULL) {
VDO_ASSERT_LOG_ONLY(!vdo_is_state_compressed(agent->new_mapped.state), "compression must have shared a lock");
VDO_ASSERT_LOG_ONLY(agent_is_done, "agent must have written the new duplicate");
transfer_allocation_lock(agent);
}
VDO_ASSERT_LOG_ONLY(vdo_is_pbn_read_lock(lock->duplicate_lock), "duplicate_lock must be a PBN read lock");
staticvoid verify_endio(struct bio *bio)
{ struct data_vio *agent = vio_as_data_vio(bio->bi_private); int result = blk_status_to_errno(bio->bi_status);
/**
* finish_locking() - Handle the result of the agent for the lock attempting to obtain a PBN read
* lock on the candidate duplicate block.
* @completion: The completion of the data_vio that attempted to get the read lock.
*
* This continuation is registered in lock_duplicate_pbn().
*/
static void finish_locking(struct vdo_completion *completion)
{
struct data_vio *agent = as_data_vio(completion);
struct hash_lock *lock = agent->hash_lock;
assert_hash_lock_agent(agent, __func__);
if (!agent->is_duplicate) {
VDO_ASSERT_LOG_ONLY(lock->duplicate_lock == NULL,
"must not hold duplicate_lock if not flagged as a duplicate");
/*
* LOCKING -> WRITING transition: The advice block is being modified or has no
* available references, so try to write or compress the data, remembering to
* update UDS later with the new advice.
*/
increment_stat(&agent->hash_zone->statistics.dedupe_advice_stale);
lock->update_advice = true;
start_writing(lock, agent);
return;
}
VDO_ASSERT_LOG_ONLY(lock->duplicate_lock != NULL,
"must hold duplicate_lock if flagged as a duplicate");
if (!lock->verified) {
/*
* LOCKING -> VERIFYING transition: Continue on the unverified dedupe path, reading
* the candidate duplicate and comparing it to the agent's data to decide whether
* it is a true duplicate or stale advice.
*/
start_verifying(lock, agent);
return;
}
if (!vdo_claim_pbn_lock_increment(lock->duplicate_lock)) {
/*
* LOCKING -> UNLOCKING transition: The verified block was re-locked, but has no
* available increments left. Must first release the useless PBN read lock before
* rolling over to a new copy of the block.
*/
agent->is_duplicate = false;
lock->verified = false;
lock->update_advice = true;
start_unlocking(lock, agent);
return;
}
/*
* LOCKING -> DEDUPING transition: Continue on the verified dedupe path, deduplicating
* against a location that was previously verified or written to.
*/
start_deduping(lock, agent, false);
}
static bool acquire_provisional_reference(struct data_vio *agent, struct pbn_lock *lock,
struct slab_depot *depot)
{
/* Ensure that the newly-locked block is referenced. */
struct vdo_slab *slab = vdo_get_slab(depot, agent->duplicate.pbn);
int result = vdo_acquire_provisional_reference(slab, agent->duplicate.pbn, lock);
/**
* lock_duplicate_pbn() - Acquire a read lock on the PBN of the block containing candidate
* duplicate data (compressed or uncompressed).
* @completion: The completion of the data_vio attempting to acquire the physical block lock on
* behalf of its hash lock.
*
* If the PBN is already locked for writing, the lock attempt is abandoned and is_duplicate will be
* cleared before calling back. This continuation is launched from start_locking(), and calls back
* to finish_locking() on the hash zone thread.
*/
static void lock_duplicate_pbn(struct vdo_completion *completion)
{
unsigned int increment_limit;
struct pbn_lock *lock;
int result;
/*
* While in the zone that owns it, find out how many additional references can be made to
* the block if it turns out to truly be a duplicate.
*/
increment_limit = vdo_get_increment_limit(depot, agent->duplicate.pbn);
if (increment_limit == 0) {
/*
* We could deduplicate against it later if a reference happened to be released
* during verification, but it's probably better to bail out now.
*/
agent->is_duplicate = false;
continue_data_vio(agent);
return;
}
result = vdo_attempt_physical_zone_pbn_lock(zone, agent->duplicate.pbn,
VIO_READ_LOCK, &lock);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(agent, result);
return;
}
if (!vdo_is_pbn_read_lock(lock)) {
/*
* There are three cases of write locks: uncompressed data block writes, compressed
* (packed) block writes, and block map page writes. In all three cases, we give up
* on trying to verify the advice and don't bother to try deduplicate against the
* data in the write lock holder.
*
* 1) We don't ever want to try to deduplicate against a block map page.
*
* 2a) It's very unlikely we'd deduplicate against an entire packed block, both
* because of the chance of matching it, and because we don't record advice for it,
* but for the uncompressed representation of all the fragments it contains. The
* only way we'd be getting lock contention is if we've written the same
* representation coincidentally before, had it become unreferenced, and it just
* happened to be packed together from compressed writes when we go to verify the
* lucky advice. Giving up is a minuscule loss of potential dedupe.
*
* 2b) If the advice is for a slot of a compressed block, it's about to get
* smashed, and the write smashing it cannot contain our data--it would have to be
* writing on behalf of our hash lock, but that's impossible since we're the lock
* agent.
*
* 3a) If the lock is held by a data_vio with different data, the advice is already
* stale or is about to become stale.
*
* 3b) If the lock is held by a data_vio that matches us, we may as well either
* write it ourselves (or reference the copy we already wrote) instead of
* potentially having many duplicates wait for the lock holder to write, journal,
* hash, and finally arrive in the hash lock. We lose a chance to avoid a UDS
* update in the very rare case of advice for a free block that just happened to be
* allocated to a data_vio with the same hash. There's also a chance to save on a
* block write, at the cost of a block verify. Saving on a full block compare in
* all stale advice cases almost certainly outweighs saving a UDS update and
* trading a write for a read in a lucky case where advice would have been saved
* from becoming stale.
*/
agent->is_duplicate = false;
continue_data_vio(agent);
return;
}
if (lock->holder_count == 0) {
if (!acquire_provisional_reference(agent, lock, depot))
return;
/*
* The increment limit we grabbed earlier is still valid. The lock now holds the
* rights to acquire all those references. Those rights will be claimed by hash
* locks sharing this read lock.
*/
lock->increment_limit = increment_limit;
}
/*
* We've successfully acquired a read lock on behalf of the hash lock, so mark it as such.
*/
set_duplicate_lock(agent->hash_lock, lock);
/*
* TODO: Optimization: We could directly launch the block verify, then switch to a hash
* thread.
*/
continue_data_vio(agent);
}
/**
* start_locking() - Continue deduplication for a hash lock that has obtained valid advice of a
* potential duplicate through its agent.
* @lock: The hash lock (currently must be QUERYING).
* @agent: The data_vio bearing the dedupe advice.
*/
static void start_locking(struct hash_lock *lock, struct data_vio *agent)
{
VDO_ASSERT_LOG_ONLY(lock->duplicate_lock == NULL,
"must not acquire a duplicate lock when already holding it");
lock->state = VDO_HASH_LOCK_LOCKING;
/*
* TODO: Optimization: If we arrange to continue on the duplicate zone thread when
* accepting the advice, and don't explicitly change lock states (or use an agent-local
* state, or an atomic), we can avoid a thread transition here.
*/
agent->last_async_operation = VIO_ASYNC_OP_LOCK_DUPLICATE_PBN;
launch_data_vio_duplicate_zone_callback(agent, lock_duplicate_pbn);
}
/**
* finish_writing() - Re-entry point for the lock agent after it has finished writing or
* compressing its copy of the data block.
* @lock: The hash lock, which must be in state WRITING.
* @agent: The data_vio that wrote its data for the lock.
*
* The agent will never need to dedupe against anything, so it's done with the lock, but the lock
* may not be finished with it, as a UDS update might still be needed.
*
* If there are other lock holders, the agent will hand the job to one of them and exit, leaving
* the lock to deduplicate against the just-written block. If there are no other lock holders, the
* agent either exits (and later tears down the hash lock), or it remains the agent and updates
* UDS.
*/
static void finish_writing(struct hash_lock *lock, struct data_vio *agent)
{
/*
* Dedupe against the data block or compressed block slot the agent wrote. Since we know
* the write succeeded, there's no need to verify it.
*/
lock->duplicate = agent->new_mapped;
lock->verified = true;
if (vdo_is_state_compressed(lock->duplicate.state) && lock->registered) {
/*
* Compression means the location we gave in the UDS query is not the location
* we're using to deduplicate.
*/
lock->update_advice = true;
}
/* If there are any waiters, we need to start deduping them. */
if (vdo_waitq_has_waiters(&lock->waiters)) {
/*
* WRITING -> DEDUPING transition: an asynchronously-written block failed to
* compress, so the PBN lock on the written copy was already transferred. The agent
* is done with the lock, but the lock may still need to use it to clean up after
* rollover.
*/
start_deduping(lock, agent, true);
return;
}
/*
* There are no waiters and the agent has successfully written, so take a step towards
* being able to release the hash lock (or just release it).
*/
if (lock->update_advice) {
/*
* WRITING -> UPDATING transition: There's no waiter and a UDS update is needed, so
* retain the WRITING agent and use it to launch the update. The happens on
* compression, rollover, or the QUERYING agent not having an allocation.
*/
start_updating(lock, agent);
} else if (lock->duplicate_lock != NULL) {
/*
* WRITING -> UNLOCKING transition: There's no waiter and no update needed, but the
* compressed write gave us a shared duplicate lock that we must release.
*/
set_duplicate_location(agent, lock->duplicate);
start_unlocking(lock, agent);
} else {
/*
* WRITING -> BYPASSING transition: There's no waiter, no update needed, and no
* duplicate lock held, so both the agent and lock have no more work to do. The
* agent will release its allocation lock in cleanup.
*/
start_bypassing(lock, agent);
}
}
/**
* select_writing_agent() - Search through the lock waiters for a data_vio that has an allocation.
* @lock: The hash lock to modify.
*
* If an allocation is found, swap agents, put the old agent at the head of the wait queue, then
* return the new agent. Otherwise, just return the current agent.
*/
static struct data_vio *select_writing_agent(struct hash_lock *lock)
{
struct vdo_wait_queue temp_queue;
struct data_vio *data_vio;
vdo_waitq_init(&temp_queue);
/*
* Move waiters to the temp queue one-by-one until we find an allocation. Not ideal to
* search, but it only happens when nearly out of space.
*/
while (((data_vio = dequeue_lock_waiter(lock)) != NULL) &&
!data_vio_has_allocation(data_vio)) {
/* Use the lower-level enqueue since we're just moving waiters around. */
vdo_waitq_enqueue_waiter(&temp_queue, &data_vio->waiter);
}
if (data_vio != NULL) {
/*
* Move the rest of the waiters over to the temp queue, preserving the order they
* arrived at the lock.
*/
vdo_waitq_transfer_all_waiters(&lock->waiters, &temp_queue);
/*
* The current agent is being replaced and will have to wait to dedupe; make it the
* first waiter since it was the first to reach the lock.
*/
vdo_waitq_enqueue_waiter(&lock->waiters, &lock->agent->waiter);
lock->agent = data_vio;
} else {
/* No one has an allocation, so keep the current agent. */
data_vio = lock->agent;
}
/* Swap all the waiters back onto the lock's queue. */
vdo_waitq_transfer_all_waiters(&temp_queue, &lock->waiters);
return data_vio;
}
/**
* start_writing() - Begin the non-duplicate write path.
* @lock: The hash lock (currently must be QUERYING).
* @agent: The data_vio currently acting as the agent for the lock.
*
* Begins the non-duplicate write path for a hash lock that had no advice, selecting a data_vio
* with an allocation as a new agent, if necessary, then resuming the agent on the data_vio write
* path.
*/
static void start_writing(struct hash_lock *lock, struct data_vio *agent)
{
lock->state = VDO_HASH_LOCK_WRITING;
/*
* The agent might not have received an allocation and so can't be used for writing, but
* it's entirely possible that one of the waiters did.
*/
if (!data_vio_has_allocation(agent)) {
agent = select_writing_agent(lock);
/* If none of the waiters had an allocation, the writes all have to fail. */
if (!data_vio_has_allocation(agent)) {
/*
* TODO: Should we keep a variant of BYPASSING that causes new arrivals to
* fail immediately if they don't have an allocation? It might be possible
* that on some path there would be non-waiters still referencing the lock,
* so it would remain in the map as everything is currently spelled, even
* if the agent and all waiters release.
*/
continue_data_vio_with_error(agent, VDO_NO_SPACE);
return;
}
}
/*
* If the agent compresses, it might wait indefinitely in the packer, which would be bad if
* there are any other data_vios waiting.
*/
if (vdo_waitq_has_waiters(&lock->waiters))
cancel_data_vio_compression(agent);
/*
* Send the agent to the compress/pack/write path in vioWrite. If it succeeds, it will
* return to the hash lock via vdo_continue_hash_lock() and call finish_writing().
*/
launch_compress_data_vio(agent);
}
/*
* Decode VDO duplicate advice from the old_metadata field of a UDS request.
* Returns true if valid advice was found and decoded
*/
static bool decode_uds_advice(struct dedupe_context *context)
{
const struct uds_request *request = &context->request;
struct data_vio *data_vio = context->requestor;
size_t offset = 0;
const struct uds_record_data *encoding = &request->old_metadata;
struct vdo *vdo = vdo_from_data_vio(data_vio);
struct zoned_pbn *advice = &data_vio->duplicate;
u8 version;
int result;
if ((request->status != UDS_SUCCESS) || !request->found)
return false;
version = encoding->data[offset++];
if (version != UDS_ADVICE_VERSION) {
vdo_log_error("invalid UDS advice version code %u", version);
return false;
}
/* Don't use advice that's clearly meaningless. */
if ((advice->state == VDO_MAPPING_STATE_UNMAPPED) || (advice->pbn == VDO_ZERO_BLOCK)) {
vdo_log_debug("Invalid advice from deduplication server: pbn %llu, state %u. Giving up on deduplication of logical block %llu",
(unsigned long long) advice->pbn, advice->state,
(unsigned long long) data_vio->logical.lbn);
atomic64_inc(&vdo->stats.invalid_advice_pbn_count);
return false;
}
result = vdo_get_physical_zone(vdo, advice->pbn, &advice->zone);
if ((result != VDO_SUCCESS) || (advice->zone == NULL)) {
vdo_log_debug("Invalid physical block number from deduplication server: %llu, giving up on deduplication of logical block %llu",
(unsigned long long) advice->pbn,
(unsigned long long) data_vio->logical.lbn);
atomic64_inc(&vdo->stats.invalid_advice_pbn_count);
return false;
}
/**
* finish_querying() - Process the result of a UDS query performed by the agent for the lock.
* @completion: The completion of the data_vio that performed the query.
*
* This continuation is registered in start_querying().
*/
static void finish_querying(struct vdo_completion *completion)
{
struct data_vio *agent = as_data_vio(completion);
struct hash_lock *lock = agent->hash_lock;
assert_hash_lock_agent(agent, __func__);
process_query_result(agent);
if (agent->is_duplicate) {
lock->duplicate = agent->duplicate;
/*
* QUERYING -> LOCKING transition: Valid advice was obtained from UDS. Use the
* QUERYING agent to start the hash lock on the unverified dedupe path, verifying
* that the advice can be used.
*/
start_locking(lock, agent);
} else {
/*
* The agent will be used as the duplicate if has an allocation; if it does, that
* location was posted to UDS, so no update will be needed.
*/
lock->update_advice = !data_vio_has_allocation(agent);
/*
* QUERYING -> WRITING transition: There was no advice or the advice wasn't valid,
* so try to write or compress the data.
*/
start_writing(lock, agent);
}
}
/**
* start_querying() - Start deduplication for a hash lock.
* @lock: The initialized hash lock.
* @data_vio: The data_vio that has just obtained the new lock.
*
* Starts deduplication for a hash lock that has finished initializing by making the data_vio that
* requested it the agent, entering the QUERYING state, and using the agent to perform the UDS
* query on behalf of the lock.
*/
static void start_querying(struct hash_lock *lock, struct data_vio *data_vio)
{
lock->agent = data_vio;
lock->state = VDO_HASH_LOCK_QUERYING;
data_vio->last_async_operation = VIO_ASYNC_OP_CHECK_FOR_DUPLICATION;
set_data_vio_hash_zone_callback(data_vio, finish_querying);
query_index(data_vio,
(data_vio_has_allocation(data_vio) ? UDS_POST : UDS_QUERY));
}
/**
* report_bogus_lock_state() - Complain that a data_vio has entered a hash_lock that is in an
* unimplemented or unusable state and continue the data_vio with an
* error.
* @lock: The hash lock.
* @data_vio: The data_vio attempting to enter the lock.
*/
static void report_bogus_lock_state(struct hash_lock *lock, struct data_vio *data_vio)
{
VDO_ASSERT_LOG_ONLY(false, "hash lock must not be in unimplemented state %s",
get_hash_lock_state_name(lock->state));
continue_data_vio_with_error(data_vio, VDO_LOCK_ERROR);
}
/**
* vdo_continue_hash_lock() - Continue the processing state after writing, compressing, or
* deduplicating.
* @completion: The data_vio completion to continue processing in its hash lock.
*
* Asynchronously continue processing a data_vio in its hash lock after it has finished writing,
* compressing, or deduplicating, so it can share the result with any data_vios waiting in the hash
* lock, or update the UDS index, or simply release its share of the lock.
*
* Context: This must only be called in the correct thread for the hash zone.
*/
void vdo_continue_hash_lock(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
struct hash_lock *lock = data_vio->hash_lock;
switch (lock->state) {
case VDO_HASH_LOCK_WRITING:
VDO_ASSERT_LOG_ONLY(data_vio == lock->agent,
"only the lock agent may continue the lock");
finish_writing(lock, data_vio);
break;
case VDO_HASH_LOCK_DEDUPING:
finish_deduping(lock, data_vio);
break;
case VDO_HASH_LOCK_BYPASSING:
/* This data_vio has finished the write path and the lock doesn't need it. */
exit_hash_lock(data_vio);
break;
case VDO_HASH_LOCK_INITIALIZING:
case VDO_HASH_LOCK_QUERYING:
case VDO_HASH_LOCK_UPDATING:
case VDO_HASH_LOCK_LOCKING:
case VDO_HASH_LOCK_VERIFYING:
case VDO_HASH_LOCK_UNLOCKING:
/* A lock in this state should never be re-entered. */
report_bogus_lock_state(lock, data_vio);
break;
/**
* is_hash_collision() - Check to see if a hash collision has occurred.
* @lock: The lock to check.
* @candidate: The data_vio seeking to share the lock.
*
* Check whether the data in data_vios sharing a lock is different than in a data_vio seeking to
* share the lock, which should only be possible in the extremely unlikely case of a hash
* collision.
*
* Return: true if the given data_vio must not share the lock because it doesn't have the same data
* as the lock holders.
*/
static bool is_hash_collision(struct hash_lock *lock, struct data_vio *candidate)
{
struct data_vio *lock_holder;
struct hash_zone *zone;
bool collides;
if (list_empty(&lock->duplicate_vios))
return false;
lock_holder = list_first_entry(&lock->duplicate_vios, struct data_vio,
hash_lock_entry);
zone = candidate->hash_zone;
collides = !blocks_equal(lock_holder->vio.data, candidate->vio.data);
if (collides)
increment_stat(&zone->statistics.concurrent_hash_collisions);
else
increment_stat(&zone->statistics.concurrent_data_matches);
return collides;
}
static inline int assert_hash_lock_preconditions(const struct data_vio *data_vio)
{
int result;
/* FIXME: BUG_ON() and/or enter read-only mode? */
result = VDO_ASSERT(data_vio->hash_lock == NULL,
"must not already hold a hash lock");
if (result != VDO_SUCCESS)
return result;
result = VDO_ASSERT(list_empty(&data_vio->hash_lock_entry),
"must not already be a member of a hash lock list");
if (result != VDO_SUCCESS)
return result;
return VDO_ASSERT(data_vio->recovery_sequence_number == 0,
"must not hold a recovery lock when getting a hash lock");
}
/**
* vdo_acquire_hash_lock() - Acquire or share a lock on a record name.
* @completion: The data_vio completion acquiring a lock on its record name.
*
* Acquire or share a lock on the hash (record name) of the data in a data_vio, updating the
* data_vio to reference the lock. This must only be called in the correct thread for the zone. In
* the unlikely case of a hash collision, this function will succeed, but the data_vio will not get
* a lock reference.
*/
void vdo_acquire_hash_lock(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
struct hash_lock *lock;
int result;
assert_data_vio_in_hash_zone(data_vio);
result = assert_hash_lock_preconditions(data_vio);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return;
}
result = acquire_lock(data_vio->hash_zone, &data_vio->record_name, NULL, &lock);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return;
}
if (is_hash_collision(lock, data_vio)) {
/*
* Hash collisions are extremely unlikely, but the bogus dedupe would be a data
* corruption. Bypass optimization entirely. We can't compress a data_vio without
* a hash_lock as the compressed write depends on the hash_lock to manage the
* references for the compressed block.
*/
write_data_vio(data_vio);
return;
}
set_hash_lock(data_vio, lock);
switch (lock->state) {
case VDO_HASH_LOCK_INITIALIZING:
start_querying(lock, data_vio);
return;
case VDO_HASH_LOCK_QUERYING:
case VDO_HASH_LOCK_WRITING:
case VDO_HASH_LOCK_UPDATING:
case VDO_HASH_LOCK_LOCKING:
case VDO_HASH_LOCK_VERIFYING:
case VDO_HASH_LOCK_UNLOCKING:
/* The lock is busy, and can't be shared yet. */
wait_on_hash_lock(lock, data_vio);
return;
case VDO_HASH_LOCK_BYPASSING:
/* We can't use this lock, so bypass optimization entirely. */
vdo_release_hash_lock(data_vio);
write_data_vio(data_vio);
return;
case VDO_HASH_LOCK_DEDUPING:
launch_dedupe(lock, data_vio, false);
return;
default:
/* A lock in this state should not be acquired by new VIOs. */
report_bogus_lock_state(lock, data_vio);
}
}
/**
* vdo_release_hash_lock() - Release a data_vio's share of a hash lock, if held, and null out the
* data_vio's reference to it.
* @data_vio: The data_vio releasing its hash lock.
*
* If the data_vio is the only one holding the lock, this also releases any resources or locks used
* by the hash lock (such as a PBN read lock on a block containing data with the same hash) and
* returns the lock to the hash zone's lock pool.
*
* Context: This must only be called in the correct thread for the hash zone.
*/
void vdo_release_hash_lock(struct data_vio *data_vio)
{
u64 lock_key;
struct hash_lock *lock = data_vio->hash_lock;
struct hash_zone *zone = data_vio->hash_zone;
if (lock == NULL)
return;
set_hash_lock(data_vio, NULL);
if (lock->reference_count > 0) {
/* The lock is still in use by other data_vios. */
return;
}
lock_key = hash_lock_key(lock);
if (lock->registered) {
struct hash_lock *removed;
removed = vdo_int_map_remove(zone->hash_lock_map, lock_key);
VDO_ASSERT_LOG_ONLY(lock == removed,
"hash lock being released must have been mapped");
} else {
VDO_ASSERT_LOG_ONLY(lock != vdo_int_map_get(zone->hash_lock_map, lock_key),
"unregistered hash lock must not be in the lock map");
}
VDO_ASSERT_LOG_ONLY(!vdo_waitq_has_waiters(&lock->waiters),
"hash lock returned to zone must have no waiters");
VDO_ASSERT_LOG_ONLY((lock->duplicate_lock == NULL),
"hash lock returned to zone must not reference a PBN lock");
VDO_ASSERT_LOG_ONLY((lock->state == VDO_HASH_LOCK_BYPASSING),
"returned hash lock must not be in use with state %s",
get_hash_lock_state_name(lock->state));
VDO_ASSERT_LOG_ONLY(list_empty(&lock->pool_node),
"hash lock returned to zone must not be in a pool list");
VDO_ASSERT_LOG_ONLY(list_empty(&lock->duplicate_vios),
"hash lock returned to zone must not reference DataVIOs");
return_hash_lock_to_pool(zone, lock);
}
/**
* transfer_allocation_lock() - Transfer a data_vio's downgraded allocation PBN lock to the
* data_vio's hash lock, converting it to a duplicate PBN lock.
* @data_vio: The data_vio holding the allocation lock to transfer.
*/
static void transfer_allocation_lock(struct data_vio *data_vio)
{
struct allocation *allocation = &data_vio->allocation;
struct hash_lock *hash_lock = data_vio->hash_lock;
VDO_ASSERT_LOG_ONLY(data_vio->new_mapped.pbn == allocation->pbn,
"transferred lock must be for the block written");
allocation->pbn = VDO_ZERO_BLOCK;
VDO_ASSERT_LOG_ONLY(vdo_is_pbn_read_lock(allocation->lock),
"must have downgraded the allocation lock before transfer");
/*
* Since the lock is being transferred, the holder count doesn't change (and isn't even
* safe to examine on this thread).
*/
hash_lock->duplicate_lock = vdo_forget(allocation->lock);
}
/**
* vdo_share_compressed_write_lock() - Make a data_vio's hash lock a shared holder of the PBN lock
* on the compressed block to which its data was just written.
* @data_vio: The data_vio which was just compressed.
* @pbn_lock: The PBN lock on the compressed block.
*
* If the lock is still a write lock (as it will be for the first share), it will be converted to a
* read lock. This also reserves a reference count increment for the data_vio.
*/
void vdo_share_compressed_write_lock(struct data_vio *data_vio,
struct pbn_lock *pbn_lock)
{
bool claimed;
VDO_ASSERT_LOG_ONLY(vdo_get_duplicate_lock(data_vio) == NULL,
"a duplicate PBN lock should not exist when writing");
VDO_ASSERT_LOG_ONLY(vdo_is_state_compressed(data_vio->new_mapped.state),
"lock transfer must be for a compressed write");
assert_data_vio_in_new_mapped_zone(data_vio);
/* First sharer downgrades the lock. */
if (!vdo_is_pbn_read_lock(pbn_lock))
vdo_downgrade_pbn_write_lock(pbn_lock, true);
/*
* Get a share of the PBN lock, ensuring it cannot be released until after this data_vio
* has had a chance to journal a reference.
*/
data_vio->duplicate = data_vio->new_mapped;
data_vio->hash_lock->duplicate = data_vio->new_mapped;
set_duplicate_lock(data_vio->hash_lock, pbn_lock);
/*
* Claim a reference for this data_vio. Necessary since another hash_lock might start
* deduplicating against it before our incRef.
*/
claimed = vdo_claim_pbn_lock_increment(pbn_lock);
VDO_ASSERT_LOG_ONLY(claimed, "impossible to fail to claim an initial increment");
}
static void start_uds_queue(void *ptr)
{
/*
* Allow the UDS dedupe worker thread to do memory allocations. It will only do allocations
* during the UDS calls that open or close an index, but those allocations can safely sleep
* while reserving a large amount of memory. We could use an allocations_allowed boolean
* (like the base threads do), but it would be an unnecessary embellishment.
*/
struct vdo_thread *thread = vdo_get_work_queue_owner(vdo_get_current_work_queue());
static void close_index(struct hash_zones *zones)
__must_hold(&zones->lock)
{
int result;
/*
* Change the index state so that get_index_statistics() will not try to use the index
* session we are closing.
*/
zones->index_state = IS_CHANGING;
/* Close the index session, while not holding the lock. */
spin_unlock(&zones->lock);
result = uds_close_index(zones->index_session);
if (result != UDS_SUCCESS)
vdo_log_error_strerror(result, "Error closing index");
spin_lock(&zones->lock);
zones->index_state = IS_CLOSED;
zones->error_flag |= result != UDS_SUCCESS;
/* ASSERTION: We leave in IS_CLOSED state. */
}
static void open_index(struct hash_zones *zones)
__must_hold(&zones->lock)
{
/* ASSERTION: We enter in IS_CLOSED state. */
int result;
bool create_flag = zones->create_flag;
zones->create_flag = false;
/*
* Change the index state so that the it will be reported to the outside world as
* "opening".
*/
zones->index_state = IS_CHANGING;
zones->error_flag = false;
/* Open the index session, while not holding the lock */
spin_unlock(&zones->lock);
result = uds_open_index(create_flag ? UDS_CREATE : UDS_LOAD,
&zones->parameters, zones->index_session);
if (result != UDS_SUCCESS)
vdo_log_error_strerror(result, "Error opening index");
spin_lock(&zones->lock);
if (!create_flag) {
switch (result) {
case -ENOENT:
/*
* Either there is no index, or there is no way we can recover the index.
* We will be called again and try to create a new index.
*/
zones->index_state = IS_CLOSED;
zones->create_flag = true;
return;
default:
break;
}
}
if (result == UDS_SUCCESS) {
zones->index_state = IS_OPENED;
} else {
zones->index_state = IS_CLOSED;
zones->index_target = IS_CLOSED;
zones->error_flag = true;
spin_unlock(&zones->lock);
vdo_log_info("Setting UDS index target state to error");
spin_lock(&zones->lock);
}
/*
* ASSERTION: On success, we leave in IS_OPENED state.
* ASSERTION: On failure, we leave in IS_CLOSED state.
*/
}
/* Loop until the index is in the target state and the create flag is clear. */
while (vdo_is_state_normal(&zones->state) &&
((zones->index_state != zones->index_target) || zones->create_flag)) {
if (zones->index_state == IS_OPENED)
close_index(zones);
else
open_index(zones);
}
/**
* report_dedupe_timeouts() - Record and eventually report that some dedupe requests reached their
* expiration time without getting answers, so we timed them out.
* @zones: the hash zones.
* @timeouts: the number of newly timed out requests.
*/
static void report_dedupe_timeouts(struct hash_zones *zones, unsigned int timeouts)
{
atomic64_add(timeouts, &zones->timeouts);
spin_lock(&zones->lock);
if (__ratelimit(&zones->ratelimiter)) {
u64 unreported = atomic64_read(&zones->timeouts);
unreported -= zones->reported_timeouts;
vdo_log_debug("UDS index timeout on %llu requests",
(unsigned long long) unreported);
zones->reported_timeouts += unreported;
}
spin_unlock(&zones->lock);
}
/*
* Since we will save up the timeouts that would have been reported but were ratelimited,
* we don't need to report ratelimiting.
*/
ratelimit_default_init(&zones->ratelimiter);
ratelimit_set_flags(&zones->ratelimiter, RATELIMIT_MSG_ON_RELEASE);
uds_offset = ((vdo_get_index_region_start(geometry) -
geometry.bio_offset) * VDO_BLOCK_SIZE);
zones->parameters = (struct uds_parameters) {
.bdev = vdo->device_config->owned_device->bdev,
.offset = uds_offset,
.size = (vdo_get_index_region_size(geometry) * VDO_BLOCK_SIZE),
.memory_size = geometry.index_config.mem,
.sparse = geometry.index_config.sparse,
.nonce = (u64) geometry.nonce,
};
result = uds_create_index_session(&zones->index_session);
if (result != UDS_SUCCESS)
return result;
result = vdo_make_thread(vdo, vdo->thread_config.dedupe_thread, &uds_queue_type, 1, NULL);
if (result != VDO_SUCCESS) {
uds_destroy_index_session(vdo_forget(zones->index_session));
vdo_log_error("UDS index queue initialization failed (%d)", result);
return result;
}
/**
* finish_index_operation() - This is the UDS callback for index queries.
* @request: The uds request which has just completed.
*/
static void finish_index_operation(struct uds_request *request)
{
struct dedupe_context *context = container_of(request, struct dedupe_context,
request);
if (change_context_state(context, DEDUPE_CONTEXT_PENDING,
DEDUPE_CONTEXT_COMPLETE)) {
/*
* This query has not timed out, so send its data_vio back to its hash zone to
* process the results.
*/
continue_data_vio(context->requestor);
return;
}
/*
* This query has timed out, so try to mark it complete and hence eligible for reuse. Its
* data_vio has already moved on.
*/
if (!change_context_state(context, DEDUPE_CONTEXT_TIMED_OUT,
DEDUPE_CONTEXT_TIMED_OUT_COMPLETE)) {
VDO_ASSERT_LOG_ONLY(false, "uds request was timed out (state %d)",
atomic_read(&context->state));
}
/**
* check_for_drain_complete() - Check whether this zone has drained.
* @zone: The zone to check.
*/
static void check_for_drain_complete(struct hash_zone *zone)
{
data_vio_count_t recycled = 0;
if (!vdo_is_state_draining(&zone->state))
return;
if ((atomic_read(&zone->timer_state) == DEDUPE_QUERY_TIMER_IDLE) ||
change_timer_state(zone, DEDUPE_QUERY_TIMER_RUNNING,
DEDUPE_QUERY_TIMER_IDLE)) {
timer_delete_sync(&zone->timer);
} else {
/*
* There is an in flight time-out, which must get processed before we can continue.
*/
return;
}
for (;;) {
struct dedupe_context *context;
struct funnel_queue_entry *entry;
entry = vdo_funnel_queue_poll(zone->timed_out_complete);
if (entry == NULL)
break;
atomic_set(&zone->timer_state, DEDUPE_QUERY_TIMER_IDLE);
list_for_each_entry_safe(context, tmp, &zone->pending, list_entry) {
if (cutoff <= context->submission_jiffies) {
/*
* We have reached the oldest query which has not timed out yet, so restart
* the timer.
*/
start_expiration_timer(context);
break;
}
if (!change_context_state(context, DEDUPE_CONTEXT_PENDING,
DEDUPE_CONTEXT_TIMED_OUT)) {
/*
* This context completed between the time the timeout fired, and now. We
* can treat it as a successful query, its requestor is already enqueued
* to process it.
*/
continue;
}
/*
* Remove this context from the pending list so we won't look at it again on a
* subsequent timeout. Once the index completes it, it will be reused. Meanwhile,
* send its requestor on its way.
*/
list_del_init(&context->list_entry);
context->requestor->dedupe_context = NULL;
continue_data_vio(context->requestor);
timed_out++;
}
if (timed_out > 0)
report_dedupe_timeouts(completion->vdo->hash_zones, timed_out);
/**
* vdo_make_hash_zones() - Create the hash zones.
*
* @vdo: The vdo to which the zone will belong.
* @zones_ptr: A pointer to hold the zones.
*
* Return: VDO_SUCCESS or an error code.
*/
int vdo_make_hash_zones(struct vdo *vdo, struct hash_zones **zones_ptr)
{
int result;
struct hash_zones *zones;
zone_count_t z;
zone_count_t zone_count = vdo->thread_config.hash_zone_count;
if (zone_count == 0)
return VDO_SUCCESS;
result = vdo_allocate_extended(struct hash_zones, zone_count, struct hash_zone,
__func__, &zones);
if (result != VDO_SUCCESS)
return result;
result = initialize_index(vdo, zones);
if (result != VDO_SUCCESS) {
vdo_free(zones);
return result;
}
static void launch_dedupe_state_change(struct hash_zones *zones)
__must_hold(&zones->lock)
{
/* ASSERTION: We enter with the lock held. */
if (zones->changing || !vdo_is_state_normal(&zones->state))
/* Either a change is already in progress, or changes are not allowed. */
return;
/**
* vdo_resume_hash_zones() - Resume a set of hash zones.
* @zones: The hash zones to resume.
* @parent: The object to notify when the zones have resumed.
*/
void vdo_resume_hash_zones(struct hash_zones *zones, struct vdo_completion *parent)
{
if (vdo_is_read_only(parent->vdo)) {
vdo_launch_completion(parent);
return;
}
/**
* get_hash_zone_statistics() - Add the statistics for this hash zone to the tally for all zones.
* @zone: The hash zone to query.
* @tally: The tally
*/
static void get_hash_zone_statistics(const struct hash_zone *zone,
struct hash_lock_statistics *tally)
{
const struct hash_lock_statistics *stats = &zone->statistics;
/**
* vdo_get_dedupe_statistics() - Tally the statistics from all the hash zones and the UDS index.
* @zones: The hash zones to query
* @stats: A structure to store the statistics
*
* Return: The sum of the hash lock statistics from all hash zones plus the statistics from the UDS
* index
*/
void vdo_get_dedupe_statistics(struct hash_zones *zones, struct vdo_statistics *stats)
{
zone_count_t zone;
for (zone = 0; zone < zones->zone_count; zone++)
get_hash_zone_statistics(&zones->zones[zone], &stats->hash_lock);
get_index_statistics(zones, &stats->index);
/*
* zones->timeouts gives the number of timeouts, and dedupe_context_busy gives the number
* of queries not made because of earlier timeouts.
*/
stats->dedupe_advice_timeouts =
(atomic64_read(&zones->timeouts) + atomic64_read(&zones->dedupe_context_busy));
}
/**
* vdo_select_hash_zone() - Select the hash zone responsible for locking a given record name.
* @zones: The hash_zones from which to select.
* @name: The record name.
*
* Return: The hash zone responsible for the record name.
*/
struct hash_zone *vdo_select_hash_zone(struct hash_zones *zones,
const struct uds_record_name *name)
{
/*
* Use a fragment of the record name as a hash code. Eight bits of hash should suffice
* since the number of hash zones is small.
* TODO: Verify that the first byte is independent enough.
*/
u32 hash = name->name[0];
/*
* Scale the 8-bit hash fragment to a zone index by treating it as a binary fraction and
* multiplying that by the zone count. If the hash is uniformly distributed over [0 ..
* 2^8-1], then (hash * count / 2^8) should be uniformly distributed over [0 .. count-1].
* The multiply and shift is much faster than a divide (modulus) on X86 CPUs.
*/
hash = (hash * zones->zone_count) >> 8;
return &zones->zones[hash];
}
/**
* dump_hash_lock() - Dump a compact description of hash_lock to the log if the lock is not on the
* free list.
* @lock: The hash lock to dump.
*/
static void dump_hash_lock(const struct hash_lock *lock)
{
const char *state;
if (!list_empty(&lock->pool_node)) {
/* This lock is on the free list. */
return;
}
/*
* Necessarily cryptic since we can log a lot of these. First three chars of state is
* unambiguous. 'U' indicates a lock not registered in the map.
*/
state = get_hash_lock_state_name(lock->state);
vdo_log_info(" hl %px: %3.3s %c%llu/%u rc=%u wc=%zu agt=%px",
lock, state, (lock->registered ? 'D' : 'U'),
(unsigned long long) lock->duplicate.pbn,
lock->duplicate.state, lock->reference_count,
vdo_waitq_num_waiters(&lock->waiters), lock->agent);
}
/**
* dump_hash_zone() - Dump information about a hash zone to the log for debugging.
* @zone: The zone to dump.
*/
static void dump_hash_zone(const struct hash_zone *zone)
{
data_vio_count_t i;
vdo_log_info("struct hash_zone %u: mapSize=%zu",
zone->zone_number, vdo_int_map_size(zone->hash_lock_map));
for (i = 0; i < LOCK_POOL_CAPACITY; i++)
dump_hash_lock(&zone->lock_array[i]);
}
/**
* vdo_dump_hash_zones() - Dump information about the hash zones to the log for debugging.
* @zones: The zones to dump.
*/
void vdo_dump_hash_zones(struct hash_zones *zones)
{
const char *state, *target;
zone_count_t zone;
vdo_log_info("UDS index: state: %s", state);
if (target != NULL)
vdo_log_info("UDS index: changing to state: %s", target);
for (zone = 0; zone < zones->zone_count; zone++)
dump_hash_zone(&zones->zones[zone]);
}
void vdo_set_dedupe_index_timeout_interval(unsigned int value)
{
u64 alb_jiffies;
/* Arbitrary maximum value is two minutes */
if (value > 120000)
value = 120000;
/* Arbitrary minimum value is 2 jiffies */
alb_jiffies = msecs_to_jiffies(value);
if (alb_jiffies < 2) {
alb_jiffies = 2;
value = jiffies_to_msecs(alb_jiffies);
}
vdo_dedupe_index_timeout_interval = value;
vdo_dedupe_index_timeout_jiffies = alb_jiffies;
}
void vdo_set_dedupe_index_min_timer_interval(unsigned int value)
{
u64 min_jiffies;
/* Arbitrary maximum value is one second */
if (value > 1000)
value = 1000;
/* Arbitrary minimum value is 2 jiffies */
min_jiffies = msecs_to_jiffies(value);
if (min_jiffies < 2) {
min_jiffies = 2;
value = jiffies_to_msecs(min_jiffies);
}
/**
* acquire_context() - Acquire a dedupe context from a hash_zone if any are available.
* @zone: the hash zone
*
* Return: A dedupe_context or NULL if none are available
*/
static struct dedupe_context * __must_check acquire_context(struct hash_zone *zone)
{
struct dedupe_context *context;
struct funnel_queue_entry *entry;
/*
* The index operation will inquire about data_vio.record_name, providing (if the operation is
* appropriate) advice from the data_vio's new_mapped fields. The advice found in the index (or
* NULL if none) will be returned via receive_data_vio_dedupe_advice(). dedupe_context.status is
* set to the return status code of any asynchronous index processing.
*/
static void query_index(struct data_vio *data_vio, enum uds_request_type operation)
{
int result;
struct dedupe_context *context;
struct vdo *vdo = vdo_from_data_vio(data_vio);
struct hash_zone *zone = data_vio->hash_zone;
assert_data_vio_in_hash_zone(data_vio);
if (!READ_ONCE(vdo->hash_zones->dedupe_flag)) {
continue_data_vio(data_vio);
return;
}
/* If create_flag, create a new index without first attempting to load an existing index. */
void vdo_start_dedupe_index(struct hash_zones *zones, bool create_flag)
{
set_target_state(zones, IS_OPENED, true, true, create_flag);
}
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.