All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fluo.core.impl.LockResolver Maven / Gradle / Ivy

There is a newer version: 1.0.0-beta-2
Show newest version
/*
 * Copyright 2014 Fluo authors (see AUTHORS)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fluo.core.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import io.fluo.accumulo.iterators.PrewriteIterator;
import io.fluo.accumulo.util.ColumnConstants;
import io.fluo.accumulo.values.DelLockValue;
import io.fluo.accumulo.values.LockValue;
import io.fluo.api.data.Column;
import io.fluo.core.util.ColumnUtil;
import io.fluo.core.util.ConditionalFlutation;
import io.fluo.core.util.FluoCondition;
import io.fluo.core.util.SpanUtil;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.ConditionalWriter;
import org.apache.accumulo.core.client.ConditionalWriter.Status;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Value;

/**
 * This is utility code for either rolling forward or back failed transactions. A transaction is deemed to have failed if the reading transaction waited too
 * long or the transactor id does not exist in zookeeper.
 */

public class LockResolver {

  private static Map>> groupLocksByPrimary(List> locks) {
    Map>> groupedLocks = new HashMap<>();
    Map transactorIds = new HashMap<>();

    for (Entry lock : locks) {
      LockValue lockVal = new LockValue(lock.getValue().get());
      PrimaryRowColumn prc = new PrimaryRowColumn(lockVal.getPrimaryRow(), lockVal.getPrimaryColumn(), lock.getKey().getTimestamp() & ColumnConstants.TIMESTAMP_MASK);

      List> lockList = groupedLocks.get(prc);
      if (lockList == null) {
        lockList = new ArrayList<>();
        groupedLocks.put(prc, lockList);
      }

      Long trid = transactorIds.get(prc);
      if (trid == null) {
        transactorIds.put(prc, lockVal.getTransactor());
      } else if (trid != lockVal.getTransactor()) {
        // sanity check.. its assumed that all locks w/ the same PrimaryRowColumn should have the same transactor id as well
        throw new IllegalStateException("transactor ids not equals " + prc + " " + lock.getKey() + " " + trid + " " + lockVal.getTransactor());
      }

      lockList.add(lock);
    }

    return groupedLocks;

  }

  /**
   * Attempts to roll forward or roll back a set of locks encountered by a transaction reading data.
   * 
   * @param env
   *          environment
   * @param startTs
   *          the logical start time from the oracle of the transaction that encountered the lock
   * @param stats
   *          stats object for the transaction that encountered the lock
   * @param locks
   * @param startTime
   *          the wall time that the transaction that encountered the lock first saw the lock
   * @return true if all locks passed in were resolved (rolled forward or back)
   */
  @SuppressWarnings("resource")
  static boolean resolveLocks(Environment env, long startTs, TxStats stats, List> locks, long startTime) {
    // check if transactor is still alive

    int numResolved = 0;

    Map mutations = new HashMap<>();

    boolean timedOut = false;

    TransactorCache transactorCache = env.getSharedResources().getTransactorCache();

    List> locksToRecover;
    if (System.currentTimeMillis() - startTime > env.getRollbackTime()) {
      locksToRecover = locks;
      stats.incrementTimedOutLocks(locksToRecover.size());
      timedOut = true;
    } else {
      locksToRecover = new ArrayList<>(locks.size());
      for (Entry entry : locks) {

        Long transactorId = new LockValue(entry.getValue().get()).getTransactor();
        long lockTs = entry.getKey().getTimestamp() & ColumnConstants.TIMESTAMP_MASK;

        if (transactorCache.checkTimedout(transactorId, lockTs)) {
          locksToRecover.add(entry);
          stats.incrementTimedOutLocks();
        } else if (!transactorCache.checkExists(transactorId)) {
          locksToRecover.add(entry);
          stats.incrementDeadLocks();
        }
      }
    }

    Map>> groupedLocks = groupLocksByPrimary(locksToRecover);

    if (timedOut) {
      Set>>> es = groupedLocks.entrySet();

      for (Entry>> entry : es) {
        long lockTs = entry.getKey().startTs;
        Long transactorId = new LockValue(entry.getValue().get(0).getValue().get()).getTransactor();
        transactorCache.addTimedoutTransactor(transactorId, lockTs, startTime);
      }
    }

    TxInfoCache txiCache = env.getSharedResources().getTxInfoCache();

    Set>>> es = groupedLocks.entrySet();
    for (Entry>> group : es) {
      TxInfo txInfo = txiCache.getTransactionInfo(group.getKey());
      switch (txInfo.status) {
        case COMMITTED:
          commitColumns(env, group.getKey(), group.getValue(), txInfo.commitTs, mutations);
          numResolved += group.getValue().size();
          break;
        case LOCKED:
          if (rollbackPrimary(env, startTs, group.getKey(), txInfo.lockValue)) {
            rollback(env, startTs, group.getKey(), group.getValue(), mutations);
            numResolved += group.getValue().size();
          }
          break;
        case ROLLED_BACK:
          // TODO ensure this if ok if there concurrent rollback
          rollback(env, startTs, group.getKey(), group.getValue(), mutations);
          numResolved += group.getValue().size();
          break;
        case UNKNOWN:
        default:
          throw new IllegalStateException("can not abort : " + group.getKey() + " (" + txInfo.status + ")");
      }
    }

    if (mutations.size() > 0)
      env.getSharedResources().getBatchWriter().writeMutations(new ArrayList<>(mutations.values()));

    return numResolved == locks.size();
  }

  private static void rollback(Environment env, long startTs, PrimaryRowColumn prc, List> value, Map mutations) {
    for (Entry entry : value) {
      if (isPrimary(prc, entry.getKey()))
        continue;

      long lockTs = entry.getKey().getTimestamp() & ColumnConstants.TIMESTAMP_MASK;
      Mutation mut = getMutation(entry.getKey().getRowData(), mutations);
      Key k = entry.getKey();
      mut.put(k.getColumnFamilyData().toArray(), k.getColumnQualifierData().toArray(), k.getColumnVisibilityParsed(), ColumnConstants.DEL_LOCK_PREFIX | startTs,
          DelLockValue.encode(lockTs, false, true));
    }

  }

  private static boolean rollbackPrimary(Environment env, long startTs, PrimaryRowColumn prc, byte[] lockValue) {
    // TODO review use of PrewriteIter here

    IteratorSetting iterConf = new IteratorSetting(10, PrewriteIterator.class);
    PrewriteIterator.setSnaptime(iterConf, startTs);
    ConditionalFlutation delLockMutation = new ConditionalFlutation(env, prc.prow, new FluoCondition(env, prc.pcol).setIterators(iterConf).setValue(lockValue));

    // TODO sanity check on lockTs vs startTs

    delLockMutation.put(prc.pcol, ColumnConstants.DEL_LOCK_PREFIX | startTs, DelLockValue.encode(prc.startTs, true, true));

    ConditionalWriter cw = null;

    cw = env.getSharedResources().getConditionalWriter();

    // TODO handle other conditional writer cases
    try {
      return cw.write(delLockMutation).getStatus() == Status.ACCEPTED;
    } catch (AccumuloException e) {
      throw new RuntimeException(e);
    } catch (AccumuloSecurityException e) {
      throw new RuntimeException(e);
    }
  }

  private static void commitColumns(Environment env, PrimaryRowColumn prc, List> value, long commitTs,
      Map mutations) {
    for (Entry entry : value) {
      if (isPrimary(prc, entry.getKey()))
        continue;

      long lockTs = entry.getKey().getTimestamp() & ColumnConstants.TIMESTAMP_MASK;
      // TODO may be that a stronger sanity check that could be done here
      if (commitTs < lockTs) {
        throw new IllegalStateException("bad commitTs : " + entry.getKey() + " (" + commitTs + "<" + lockTs + ")");
      }

      Mutation mut = getMutation(entry.getKey().getRowData(), mutations);
      Column col = SpanUtil.toRowColumn(entry.getKey()).getColumn();
      
      LockValue lv = new LockValue(entry.getValue().get());
      ColumnUtil.commitColumn(env, lv.isTrigger(), false, col, lv.isWrite(), lv.isDelete(), lockTs, commitTs, env.getObservers().keySet(), mut);
    }

  }

  private static Mutation getMutation(ByteSequence row, Map mutations) {
    Mutation mut = mutations.get(row);

    if (mut == null) {
      mut = new Mutation(row.toArray());
      mutations.put(row, mut);
    }

    return mut;
  }

  private static boolean isPrimary(PrimaryRowColumn prc, Key k) {
    return prc.prow.equals(k.getRowData()) && prc.pcol.equals(SpanUtil.toRowColumn(k).getColumn());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy