func (rc *ResponseCache) decodeResponseCacheKey(encKey proto.EncodedKey) (proto.ClientCmdID, error) { ret := proto.ClientCmdID{} key, _, isValue := engine.MVCCDecodeKey(encKey) if isValue { return ret, util.Errorf("key %s is not a raw MVCC value", encKey) } if !bytes.HasPrefix(key, keys.LocalRangeIDPrefix) { return ret, util.Errorf("key %s does not have %s prefix", key, keys.LocalRangeIDPrefix) } // Cut the prefix and the Raft ID. b := key[len(keys.LocalRangeIDPrefix):] b, _ = encoding.DecodeUvarint(b) if !bytes.HasPrefix(b, keys.LocalResponseCacheSuffix) { return ret, util.Errorf("key %s does not contain the response cache suffix %s", key, keys.LocalResponseCacheSuffix) } // Cut the response cache suffix. b = b[len(keys.LocalResponseCacheSuffix):] // Now, decode the command ID. b, wt := encoding.DecodeUvarint(b) b, rd := encoding.DecodeUint64(b) if len(b) > 0 { return ret, util.Errorf("key %s has leftover bytes after decode: %s; indicates corrupt key", encKey, b) } ret.WallTime = int64(wt) ret.Random = int64(rd) return ret, nil }
// GetResponse looks up a response matching the specified cmdID and // returns true if found. The response is deserialized into the // supplied reply parameter. If no response is found, returns // false. If a command is pending already for the cmdID, then this // method will block until the the command is completed or the // response cache is cleared. func (rc *ResponseCache) GetResponse(e engine.Engine, cmdID proto.ClientCmdID, reply proto.Response) (bool, error) { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return false, nil } // Pull response from the cache and read into reply if available. rwResp := proto.ReadWriteCmdResponse{} key := keys.ResponseCacheKey(rc.raftID, &cmdID) ok, err := engine.MVCCGetProto(e, key, proto.ZeroTimestamp, true, nil, &rwResp) if ok && err == nil { gogoproto.Merge(reply, rwResp.GetValue().(gogoproto.Message)) } return ok, err }
// GetResponse looks up a response matching the specified cmdID and // returns true if found. The response is deserialized into the // supplied reply parameter. If no response is found, returns // false. If a command is pending already for the cmdID, then this // method will block until the the command is completed or the // response cache is cleared. func (rc *ResponseCache) GetResponse(cmdID proto.ClientCmdID, reply proto.Response) (bool, error) { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return false, nil } // If the response is in the cache or we experienced an error, return. rwResp := proto.ReadWriteCmdResponse{} key := keys.ResponseCacheKey(rc.raftID, &cmdID) ok, err := engine.MVCCGetProto(rc.engine, key, proto.ZeroTimestamp, true, nil, &rwResp) if ok && err == nil { gogoproto.Merge(reply, rwResp.GetValue().(gogoproto.Message)) } return ok, err }
// PutResponse writes a response to the cache for the specified cmdID. func (rc *ResponseCache) PutResponse(e engine.Engine, cmdID proto.ClientCmdID, reply proto.Response) error { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return nil } // Write the response value to the engine. var err error if rc.shouldCacheResponse(reply) { key := keys.ResponseCacheKey(rc.raftID, &cmdID) rwResp := &proto.ReadWriteCmdResponse{} if !rwResp.SetValue(reply) { log.Fatalf("response %T not supported by response cache", reply) } err = engine.MVCCPutProto(e, nil, key, proto.ZeroTimestamp, nil, rwResp) } return err }
// PutResponse writes a response to the cache for the specified cmdID. // The inflight entry corresponding to cmdID is removed from the // inflight map. Any requests waiting on the outcome of the inflight // command will be signaled to wakeup and read the command response // from the cache. func (rc *ResponseCache) PutResponse(cmdID proto.ClientCmdID, reply interface{}) error { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return nil } // Write the response value to the engine. key := rc.makeKey(cmdID) rwResp := &proto.ReadWriteCmdResponse{} rwResp.SetValue(reply) err := engine.PutProto(rc.engine, key, rwResp) // Take lock after writing response to cache! rc.Lock() defer rc.Unlock() // Even on error, we remove the entry from the inflight map. rc.removeInflightLocked(cmdID) return err }
// GetResponse looks up a response matching the specified cmdID. If the // response is found, it is returned along with its associated error. // If the response is not found, nil is returned for both the response // and its error. In all cases, the third return value is the error // returned from the engine when reading the on-disk cache. func (rc *ResponseCache) GetResponse(e engine.Engine, cmdID proto.ClientCmdID) (proto.ResponseWithError, error) { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return proto.ResponseWithError{}, nil } // Pull response from the cache and read into reply if available. br := &proto.BatchResponse{} key := keys.ResponseCacheKey(rc.rangeID, &cmdID) ok, err := engine.MVCCGetProto(e, key, proto.ZeroTimestamp, true, nil, br) if err != nil { return proto.ResponseWithError{}, err } if ok { header := br.Header() defer func() { header.Error = nil }() return proto.ResponseWithError{Reply: br, Err: header.GoError()}, nil } return proto.ResponseWithError{}, nil }
// PutResponse writes a response and an error associated with it to the // cache for the specified cmdID. func (rc *ResponseCache) PutResponse(e engine.Engine, cmdID proto.ClientCmdID, replyWithErr proto.ResponseWithError) error { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return nil } // Write the response value to the engine. if rc.shouldCacheResponse(replyWithErr) { // Write the error into the reply before caching. header := replyWithErr.Reply.Header() header.SetGoError(replyWithErr.Err) // Be sure to clear it when you're done! defer func() { header.Error = nil }() key := keys.ResponseCacheKey(rc.rangeID, &cmdID) return engine.MVCCPutProto(e, nil, key, proto.ZeroTimestamp, nil, replyWithErr.Reply) } return nil }
// GetResponse looks up a response matching the specified cmdID. If the // response is found, it is returned along with its associated error. // If the response is not found, nil is returned for both the response // and its error. In all cases, the third return value is the error // returned from the engine when reading the on-disk cache. func (rc *ResponseCache) GetResponse(e engine.Engine, cmdID proto.ClientCmdID) (proto.ResponseWithError, error) { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return proto.ResponseWithError{}, nil } // Pull response from the cache and read into reply if available. var rwResp proto.ReadWriteCmdResponse key := keys.ResponseCacheKey(rc.raftID, &cmdID) ok, err := engine.MVCCGetProto(e, key, proto.ZeroTimestamp, true, nil, &rwResp) if err != nil { return proto.ResponseWithError{}, err } if ok { resp := rwResp.GetValue().(proto.Response) header := resp.Header() defer func() { header.Error = nil }() return proto.ResponseWithError{resp, header.GoError()}, nil } return proto.ResponseWithError{}, nil }
// PutResponse writes a response to the cache for the specified cmdID. // The inflight entry corresponding to cmdID is removed from the // inflight map. Any requests waiting on the outcome of the inflight // command will be signaled to wakeup and read the command response // from the cache. func (rc *ResponseCache) PutResponse(cmdID proto.ClientCmdID, reply proto.Response) error { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return nil } // Write the response value to the engine. var err error if rc.shouldCacheResponse(reply) { key := engine.ResponseCacheKey(rc.raftID, &cmdID) rwResp := &proto.ReadWriteCmdResponse{} rwResp.SetValue(reply) err = engine.MVCCPutProto(rc.engine, nil, key, proto.ZeroTimestamp, nil, rwResp) } // Take lock after writing response to cache! rc.Lock() defer rc.Unlock() // Even on error, we remove the entry from the inflight map. rc.removeInflightLocked(cmdID) return err }
// GetResponse looks up a response matching the specified cmdID and // returns true if found. The response is deserialized into the // supplied reply parameter. If no response is found, returns // false. If a command is pending already for the cmdID, then this // method will block until the the command is completed or the // response cache is cleared. func (rc *ResponseCache) GetResponse(cmdID proto.ClientCmdID, reply proto.Response) (bool, error) { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return false, nil } // If the command is inflight, wait for it to complete. rc.Lock() for { if cond, ok := rc.inflight[makeCmdIDKey(cmdID)]; ok { log.Infof("waiting on cmdID: %s", &cmdID) cond.Wait() } else { break } } // Adding inflight here is preemptive; we don't want to hold lock // while fetching from the on-disk cache. The vast, vast majority of // calls to GetResponse will be cache misses, so this saves us // from acquiring the lock twice: once here and once below in the // event we experience a cache miss. rc.addInflightLocked(cmdID) rc.Unlock() // If the response is in the cache or we experienced an error, return. rwResp := proto.ReadWriteCmdResponse{} key := engine.ResponseCacheKey(rc.raftID, &cmdID) if ok, err := engine.MVCCGetProto(rc.engine, key, proto.ZeroTimestamp, nil, &rwResp); ok || err != nil { rc.Lock() // Take lock after fetching response from cache. defer rc.Unlock() rc.removeInflightLocked(cmdID) if err == nil && rwResp.GetValue() != nil { gogoproto.Merge(reply.(gogoproto.Message), rwResp.GetValue().(gogoproto.Message)) } return ok, err } // There's no command result cached for this ID; but inflight was added above. return false, nil }
// PutResponse writes a response and an error associated with it to the // cache for the specified cmdID. func (rc *ResponseCache) PutResponse(e engine.Engine, cmdID proto.ClientCmdID, replyWithErr proto.ResponseWithError) error { // Do nothing if command ID is empty. if cmdID.IsEmpty() { return nil } // Write the response value to the engine. if rc.shouldCacheResponse(replyWithErr) { // Write the error into the reply before caching. header := replyWithErr.Reply.Header() header.SetGoError(replyWithErr.Err) // Be sure to clear it when you're done! defer func() { header.Error = nil }() key := keys.ResponseCacheKey(rc.raftID, &cmdID) var rwResp proto.ReadWriteCmdResponse if !rwResp.SetValue(replyWithErr.Reply) { panic(fmt.Sprintf("response %T not supported by response cache", replyWithErr.Reply)) } return engine.MVCCPutProto(e, nil, key, proto.ZeroTimestamp, nil, &rwResp) } return nil }