func (s *sequenceHasher) loadClocks(hashValue uint64) (*storedClocks, error) { stored := storedClocks{} key := kHashPrefix + strconv.FormatUint(hashValue, 10) bytes, cas, err := s.bucket.GetAndTouchRaw(key, base.SecondsToCbsExpiry(int(s.hashExpiry))) IndexExpvars.Add("get_hashLoadClocks", 1) if err != nil { // Assume no clocks stored for this string return &stored, nil } if err = stored.Unmarshal(bytes); err != nil { base.Warn("Error unmarshalling stored clocks for key [%s], returning zero sequence", key) return &stored, errors.New("Error unmarshalling stored clocks for key") } stored.cas = cas return &stored, nil }
func (s *sequenceHasher) GetHash(clock base.SequenceClock) (string, error) { if clock == nil { return "", errors.New("Can't calculate hash for nil clock") } hashValue := s.calculateHash(clock) // Load stored clocks for this hash, to see if it's already been defined. // Note: getCacheValue and load are handled as separate operations to optimize locking. // 1. getCacheValue locks the cache, and retrieves the current cache entry (or creates a new empty entry if not found) // 2. cachedValue.load locks the entry, and loads from the DB if no previous entry is found cachedValue := s.getCacheValue(hashValue) cachedClocks, err := cachedValue.load(s.loadClocks) if err != nil { return "", err } // Check whether the cached clocks for the hash value match our clock exists, index := cachedClocks.Contains(clock.Value()) if exists { seqHash := sequenceHash{ hashValue: hashValue, collisionIndex: uint16(index), } IndexExpvars.Add("seqHash_getHash_hits", 1) return seqHash.String(), nil } // Didn't find a match in cache - update the index and the cache. Get a write lock on the index value // first, to ensure only one goroutine on this SG attempts to write. writeCas handling below handles // the case where other SGs are updating the value concurrently IndexExpvars.Add("seqHash_getHash_misses", 1) // First copy the clock value, to ensure we store a non-mutable version in the cache clockValue := make([]uint64, len(clock.Value())) copy(clockValue, clock.Value()) updateErr := func() error { cachedValue.lock.Lock() defer cachedValue.lock.Unlock() // If the number of cached clocks has changed, check whether someone else has added this clock // while we waited for the lock if len(cachedValue.clocks.Sequences) > len(cachedClocks.Sequences) { exists, index = cachedValue.clocks.Contains(clockValue) if exists { return nil } } // Add our clock to the cached clocks for this hash existingClocks := cachedValue.clocks existingClocks.Sequences = append(existingClocks.Sequences, clockValue) // Update the hash entry in the bucket key := kHashPrefix + strconv.FormatUint(hashValue, 10) initialValue, err := existingClocks.Marshal() index = len(existingClocks.Sequences) - 1 if err != nil { return err } _, err = base.WriteCasRaw(s.bucket, key, initialValue, existingClocks.cas, base.SecondsToCbsExpiry(int(s.hashExpiry)), func(value []byte) (updatedValue []byte, err error) { // Note: The following is invoked upon cas failure - may be called multiple times base.LogTo("DIndex+", "CAS fail - reapplying changes for hash storage for key: %s", key) var sClocks storedClocks err = sClocks.Unmarshal(value) if err != nil { base.Warn("Error unmarshalling hash storage during update", err) return nil, err } exists, index = sClocks.Contains(clockValue) if exists { // return empty byte array to cancel the update return []byte{}, nil } // Not found - add sClocks.Sequences = append(sClocks.Sequences, clockValue) base.LogTo("DIndex+", "Reattempting stored hash write for key %s:", key) index = len(sClocks.Sequences) - 1 return sClocks.Marshal() }) return nil }() if updateErr != nil { return "", updateErr } IndexExpvars.Add("writeCasRaw_hash", 1) if err != nil && err.Error() != "Already Exists" { return "", err } seqHash := &sequenceHash{ hashValue: hashValue, collisionIndex: uint16(index), } return seqHash.String(), nil }