func (bucket CouchbaseBucketGoCB) WriteCas(k string, flags int, exp int, cas uint64, v interface{}, opt sgbucket.WriteOptions) (casOut uint64, err error) { bucket.singleOps <- struct{}{} defer func() { <-bucket.singleOps }() // we only support the sgbucket.Raw WriteOption at this point if opt != sgbucket.Raw { LogPanic("WriteOption must be sgbucket.Raw") } // also, flags must be 0, since that is not supported by gocb if flags != 0 { LogPanic("flags must be 0") } worker := func() (shouldRetry bool, err error, value interface{}) { if cas == 0 { // Try to insert the value into the bucket gocbExpvars.Add("WriteCas_Insert", 1) newCas, err := bucket.Bucket.Insert(k, v, uint32(exp)) shouldRetry = isRecoverableGoCBError(err) return shouldRetry, err, uint64(newCas) } // Otherwise, replace existing value gocbExpvars.Add("WriteCas_Replace", 1) newCas, err := bucket.Bucket.Replace(k, v, gocb.Cas(cas), uint32(exp)) shouldRetry = isRecoverableGoCBError(err) return shouldRetry, err, uint64(newCas) } sleeper := CreateDoublingSleeperFunc( bucket.spec.MaxNumRetries, bucket.spec.InitialRetrySleepTimeMS, ) // Kick off retry loop description := fmt.Sprintf("WriteCas with key %v", k) err, result := RetryLoop(description, worker, sleeper) // If the retry loop returned a nil result, set to 0 to prevent type assertion on nil error if result == nil { result = uint64(0) } // Type assertion of result cas, ok := result.(uint64) if !ok { return 0, fmt.Errorf("Error doing type assertion of %v into a uint64, Key: %v", result, k) } return cas, err }
func (bucket CouchbaseBucketGoCB) processBulkSetEntriesBatch(entries []*sgbucket.BulkSetEntry) (error, []*sgbucket.BulkSetEntry) { retryEntries := []*sgbucket.BulkSetEntry{} var items []gocb.BulkOp for _, entry := range entries { switch entry.Cas { case 0: // if no CAS val, treat it as an insert (similar to WriteCas()) item := &gocb.InsertOp{ Key: entry.Key, Value: entry.Value, } items = append(items, item) default: // otherwise, treat it as a replace item := &gocb.ReplaceOp{ Key: entry.Key, Value: entry.Value, Cas: gocb.Cas(entry.Cas), } items = append(items, item) } } // Do the underlying bulk operation if err := bucket.Do(items); err != nil { return err, retryEntries } for index, item := range items { entry := entries[index] switch item := item.(type) { case *gocb.InsertOp: entry.Cas = uint64(item.Cas) entry.Error = item.Err if item.Err != nil && isRecoverableGoCBError(item.Err) { retryEntries = append(retryEntries, entry) } case *gocb.ReplaceOp: entry.Cas = uint64(item.Cas) entry.Error = item.Err if item.Err != nil && isRecoverableGoCBError(item.Err) { retryEntries = append(retryEntries, entry) } } } return nil, retryEntries }
func (bucket CouchbaseBucketGoCB) Update(k string, exp int, callback sgbucket.UpdateFunc) error { bucket.singleOps <- struct{}{} defer func() { <-bucket.singleOps }() maxCasRetries := 100000 // prevent infinite loop for i := 0; i < maxCasRetries; i++ { var value interface{} var err error // Load the existing value. // NOTE: ignore error and assume it's a "key not found" error. If it's a more // serious error, it will probably recur when calling other ops below gocbExpvars.Add("Update_Get", 1) cas, _ := bucket.Get(k, &value) var callbackParam []byte if value != nil { callbackParam = value.([]byte) } // Invoke callback to get updated value value, err = callback(callbackParam) if err != nil { return err } if cas == 0 { // If the Get fails, the cas will be 0 and so call Insert(). // If we get an error on the insert, due to a race, this will // go back through the cas loop gocbExpvars.Add("Update_Insert", 1) _, err = bucket.Bucket.Insert(k, value, uint32(exp)) } else { if value == nil { // In order to match the go-couchbase bucket behavior, if the // callback returns nil, we delete the doc gocbExpvars.Add("Update_Remove", 1) _, err = bucket.Bucket.Remove(k, gocb.Cas(cas)) } else { // Otherwise, attempt to do a replace. won't succeed if // updated underneath us gocbExpvars.Add("Update_Replace", 1) _, err = bucket.Bucket.Replace(k, value, gocb.Cas(cas), uint32(exp)) } } // If there was no error, we're done if err == nil { return nil } } return fmt.Errorf("Failed to update after %v CAS attempts", maxCasRetries) }