package org.corfudb.runtime.view.replication; import lombok.extern.slf4j.Slf4j; import org.corfudb.protocols.wireprotocol.ILogData; import org.corfudb.runtime.exceptions.OverwriteException; import org.corfudb.runtime.exceptions.RecoveryException; import org.corfudb.runtime.view.Layout; import org.corfudb.util.CFUtils; import javax.annotation.Nullable; /** * Created by mwei on 4/6/17. */ @Slf4j public class ChainReplicationProtocol extends AbstractReplicationProtocol { public ChainReplicationProtocol(IHoleFillPolicy holeFillPolicy) { super(holeFillPolicy); } /** {@inheritDoc} */ @Override public void write(Layout layout, ILogData data) throws OverwriteException { final long globalAddress = data.getGlobalAddress(); int numUnits = layout.getSegmentLength(globalAddress); // To reduce the overhead of serialization, we serialize only the // first time we write, saving when we go down the chain. try (ILogData.SerializationHandle sh = data.getSerializedForm()) { log.trace("Write[{}]: chain head {}/{}", globalAddress, 1, numUnits); // In chain replication, we start at the chain head. try { CFUtils.getUninterruptibly( layout.getLogUnitClient(globalAddress, 0) .write(sh.getSerialized()) , OverwriteException.class); propagate(layout, globalAddress, sh.getSerialized()); } catch (OverwriteException oe) { // Some other wrote here (usually due to hole fill) // We need to invoke the recovery protocol, in case // the write wasn't driven to completion. recover(layout, globalAddress); throw oe; } } } /** {@inheritDoc} */ @Override public ILogData peek(Layout layout, long globalAddress) { int numUnits = layout.getSegmentLength(globalAddress); log.trace("Read[{}]: chain {}/{}", globalAddress, numUnits, numUnits); // In chain replication, we read from the last unit, though we can optimize if we // know where the committed tail is. ILogData ret = CFUtils.getUninterruptibly(layout .getLogUnitClient(globalAddress, numUnits - 1) .read(globalAddress)).getReadSet() .getOrDefault(globalAddress, null); return ret == null || ret.isEmpty() ? null : ret; } /** Propagate a write down the chain, ignoring * any overwrite errors. It is expected that the * write has already successfully completed at * the head of the chain. * * @param layout The layout to use for propagation. * @param globalAddress The global address to start * writing at. * @param data The data to propagate, or NULL, * if it is to be a hole. */ protected void propagate(Layout layout, long globalAddress, @Nullable ILogData data) { int numUnits = layout.getSegmentLength(globalAddress); for (int i = 1; i < numUnits; i++) { log.trace("Propogate[{}]: chain {}/{}", globalAddress, i + 1, numUnits); // In chain replication, we write synchronously to every unit // in the chain. try { if (data != null) { CFUtils.getUninterruptibly( layout.getLogUnitClient(globalAddress, i) .write(data) , OverwriteException.class); } else { CFUtils.getUninterruptibly(layout.getLogUnitClient(globalAddress, i) .fillHole(globalAddress), OverwriteException.class); } } catch (OverwriteException oe) { log.trace("Propogate[{}]: Completed by other writer", globalAddress); } } } /** Recover a failed write at the given global address, * driving it to completion by invoking the recovery * protocol. * * When this function returns the given globalAddress * is guaranteed to contain a committed value. * * If there was no data previously written at the address, * this function will throw a runtime exception. The * recovery protocol should -only- be invoked if we * previously were overwritten. * * @oaram layout The layout to use for the recovery. * @param globalAddress The global address to drive * the recovery protocol * */ protected void recover(Layout layout, long globalAddress) { // In chain replication, we started writing from the head, // and propagated down to the tail. To recover, we start // reading from the head, which should have the data // we are trying to recover int numUnits = layout.getSegmentLength(globalAddress); log.debug("Recover[{}]: read chain head {}/{}", globalAddress, 1, numUnits); ILogData ld = CFUtils.getUninterruptibly(layout.getLogUnitClient(globalAddress, 0) .read(globalAddress)).getReadSet().getOrDefault(globalAddress, null); // If nothing was at the head, this is a bug and we // should fail with a runtime exception, as there // was nothing to recover - if the head was removed // due to a reconfiguration, a network exception // would have been thrown and the client should have // retried it's operation (in this case of a write, // it should have read to determine whether the // write was successful or not. if (ld == null || ld.isEmpty()) { throw new RecoveryException("Failed to read data during recovery at chain head."); } // now we go down the chain and write, ignoring any overwrite exception we get. for (int i = 1; i < numUnits; i++) { log.debug("Recover[{}]: write chain {}/{}", layout, i + 1, numUnits); // In chain replication, we write synchronously to every unit // in the chain. try { CFUtils.getUninterruptibly( layout.getLogUnitClient(globalAddress, i) .write(ld) , OverwriteException.class); // We successfully recovered a write to this member of the chain log.debug("Recover[{}]: recovered write at chain {}/{}", layout, i + 1, numUnits); } catch (OverwriteException oe) { // This member already had this data (in some cases, the write might have // been committed to all members, so this is normal). log.debug("Recover[{}]: overwritten at chain {}/{}", layout, i + 1, numUnits); } } } /** {@inheritDoc} */ @Override protected void holeFill(Layout layout, long globalAddress) { int numUnits = layout.getSegmentLength(globalAddress); log.trace("fillHole[{}]: chain head {}/{}", globalAddress, 1, numUnits); // In chain replication, we write synchronously to every unit in // the chain. try { CFUtils.getUninterruptibly(layout.getLogUnitClient(globalAddress, 0) .fillHole(globalAddress), OverwriteException.class); propagate(layout, globalAddress, null); } catch (OverwriteException oe) { // The hole-fill failed. We must ensure the other writer's // value is adopted before returning. recover(layout, globalAddress); } } }