// TestClock performs a complete test of all basic phenomena, // including backward jumps in local physical time and clock offset. func TestClock(t *testing.T) { m := NewManualClock(0) c := NewClock(m.UnixNano) c.SetMaxOffset(1000) expectedHistory := []struct { // The physical time that this event should take place at. wallClock int64 event Event // If this is a receive event, this holds the "input" timestamp. input *proto.Timestamp // The expected timestamp generated from the input. expected proto.Timestamp }{ // A few valid steps to warm up. {5, SEND, nil, proto.Timestamp{WallTime: 5, Logical: 0}}, {6, SEND, nil, proto.Timestamp{WallTime: 6, Logical: 0}}, {10, RECV, &proto.Timestamp{WallTime: 10, Logical: 5}, proto.Timestamp{WallTime: 10, Logical: 6}}, // Our clock mysteriously jumps back. {7, SEND, nil, proto.Timestamp{WallTime: 10, Logical: 7}}, // Wall clocks coincide, but the local logical clock wins. {8, RECV, &proto.Timestamp{WallTime: 10, Logical: 4}, proto.Timestamp{WallTime: 10, Logical: 8}}, // The next message comes from a faulty clock and should // be discarded. {9, RECV, &proto.Timestamp{WallTime: 1100, Logical: 888}, proto.Timestamp{WallTime: 10, Logical: 8}}, // Wall clocks coincide, but the remote logical clock wins. {10, RECV, &proto.Timestamp{WallTime: 10, Logical: 99}, proto.Timestamp{WallTime: 10, Logical: 100}}, // The physical clock has caught up and takes over. {11, RECV, &proto.Timestamp{WallTime: 10, Logical: 31}, proto.Timestamp{WallTime: 11, Logical: 0}}, {11, SEND, nil, proto.Timestamp{WallTime: 11, Logical: 1}}, } var current proto.Timestamp var err error for i, step := range expectedHistory { m.Set(step.wallClock) switch step.event { case SEND: current = c.Now() case RECV: fallthrough default: previous := c.Timestamp() current, err = c.Update(*step.input) if current.Equal(previous) && err == nil { t.Errorf("%d: clock not updated even though no error occurred", i) } } if !current.Equal(step.expected) { t.Fatalf("HLC error: %d expected %v, got %v", i, step.expected, current) } } c.Now() }
// TODO(Tobias): Turn this into a writebatch with account stats in a reusable way. // This requires use of RocksDB's merge operator to implement increasable counters func (mvcc *MVCC) putInternal(key Key, timestamp proto.Timestamp, value []byte, txnID []byte) error { meta := &proto.MVCCMetadata{} ok, err := GetProto(mvcc.engine, key, meta) if err != nil { return err } // In case the key metadata exists. if ok { // There is an uncommitted write intent and the current Put // operation does not come from the same transaction. // This should not happen since range should check the existing // write intent before executing any Put action at MVCC level. if len(meta.TxnID) > 0 && (len(txnID) == 0 || !bytes.Equal(meta.TxnID, txnID)) { return &writeIntentError{TxnID: meta.TxnID} } if meta.Timestamp.Less(timestamp) || (timestamp.Equal(meta.Timestamp) && bytes.Equal(meta.TxnID, txnID)) { // Update MVCC metadata. meta = &proto.MVCCMetadata{TxnID: txnID, Timestamp: timestamp} if err := PutProto(mvcc.engine, key, meta); err != nil { return err } } else { // In case we receive a Put request to update an old version, // it must be an error since raft should handle any client // retry from timeout. return &writeTimestampTooOldError{Timestamp: meta.Timestamp} } } else { // In case the key metadata does not exist yet. // Create key metadata. meta = &proto.MVCCMetadata{TxnID: txnID, Timestamp: timestamp} if err := PutProto(mvcc.engine, key, meta); err != nil { return err } } // Save the value with the given version (Key + Timestamp). return mvcc.engine.Put(mvccEncodeKey(key, timestamp), value) }
// putInternal adds a new timestamped value to the specified key. // If value is nil, creates a deletion tombstone value. func (mvcc *MVCC) putInternal(key Key, timestamp proto.Timestamp, value proto.MVCCValue, txn *proto.Transaction) error { if value.Value != nil && value.Value.Bytes != nil && value.Value.Integer != nil { return util.Errorf("key %q value contains both a byte slice and an integer value: %+v", key, value) } meta := &proto.MVCCMetadata{} ok, err := GetProto(mvcc.engine, key, meta) if err != nil { return err } // Use a batch because a put involves multiple writes. var batch []interface{} // In case the key metadata exists. if ok { // There is an uncommitted write intent and the current Put // operation does not come from the same transaction. // This should not happen since range should check the existing // write intent before executing any Put action at MVCC level. if meta.Txn != nil && (txn == nil || !bytes.Equal(meta.Txn.ID, txn.ID)) { return &writeIntentError{Txn: meta.Txn} } // We can update the current metadata only if both the timestamp // and epoch of the new intent are greater than or equal to // existing. If either of these conditions doesn't hold, it's // likely the case that an older RPC is arriving out of order. if !timestamp.Less(meta.Timestamp) && (meta.Txn == nil || txn.Epoch >= meta.Txn.Epoch) { // If this is an intent and timestamps have changed, need to remove old version. if meta.Txn != nil && !timestamp.Equal(meta.Timestamp) { batch = append(batch, BatchDelete(mvccEncodeKey(key, meta.Timestamp))) } meta = &proto.MVCCMetadata{Txn: txn, Timestamp: timestamp} batchPut, err := MakeBatchPutProto(key, meta) if err != nil { return err } batch = append(batch, batchPut) } else { // In case we receive a Put request to update an old version, // it must be an error since raft should handle any client // retry from timeout. return &writeTooOldError{Timestamp: meta.Timestamp, Txn: meta.Txn} } } else { // In case the key metadata does not exist yet. // Create key metadata. meta = &proto.MVCCMetadata{Txn: txn, Timestamp: timestamp} batchPut, err := MakeBatchPutProto(key, meta) if err != nil { return err } batch = append(batch, batchPut) } // Make sure to zero the redundant timestamp (timestamp is encoded // into the key, so don't need it in both places). if value.Value != nil { value.Value.Timestamp = nil } batchPut, err := MakeBatchPutProto(mvccEncodeKey(key, timestamp), &value) if err != nil { return err } batch = append(batch, batchPut) return mvcc.engine.WriteBatch(batch) }