// CopyFrom copies all the cached results from another response cache // into this one. Note that the cache will not be locked while copying // is in progress. Failures decoding individual cache entries return an // error. The copy is done directly using the engine instead of interpreting // values through MVCC for efficiency. func (rc *ResponseCache) CopyFrom(e engine.Engine, originRaftID int64) error { prefix := engine.ResponseCacheKey(originRaftID, nil) // response cache prefix start := engine.MVCCEncodeKey(prefix) end := engine.MVCCEncodeKey(prefix.PrefixEnd()) return e.Iterate(start, end, func(kv proto.RawKeyValue) (bool, error) { // Decode the key into a cmd, skipping on error. Otherwise, // write it to the corresponding key in the new cache. cmdID, err := rc.decodeResponseCacheKey(kv.Key) if err != nil { return false, util.Errorf("could not decode a response cache key %q: %s", kv.Key, err) } encKey := engine.MVCCEncodeKey(engine.ResponseCacheKey(rc.raftID, &cmdID)) return false, rc.engine.Put(encKey, kv.Value) }) }
// 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 }
// ClearData removes all items stored in the persistent cache. It does not alter // the inflight map. func (rc *ResponseCache) ClearData() error { p := engine.ResponseCacheKey(rc.raftID, nil) // prefix for all response cache entries with this raft ID end := p.PrefixEnd() _, err := engine.ClearRange(rc.engine, engine.MVCCEncodeKey(p), engine.MVCCEncodeKey(end)) return err }