Introduction to OCC
Optimistic Concurrency Control (OCC) is a strategy used to manage shared resources and prevent "lost updates" or race conditions.
In Google Cloud C++ libraries, IAM Policy objects contain an etag field. When
calling SetIamPolicy, the client library serializes this etag. If the server
detects that the etag provided does not match the current version on the
server, it returns a kAborted (or sometimes kFailedPrecondition) status.
Implementing the OCC Loop in C++
The core of the implementation is a while loop that checks the Status
returned by the API call.
Steps of the Loop
| Step | Action | C++ Implementation |
|---|---|---|
| 1. Read | Fetch the current IAM Policy. | client.GetIamPolicy(name) |
| 2. Modify | Apply changes to the google::iam::v1::Policy object. |
Modify repeated fields (bindings). |
| 3. Write | Attempt to set the policy. | client.SetIamPolicy(name, policy) |
| 4. Retry | Check Status.code(). |
if (status.code() == StatusCode::kAborted) continue; |
C++ Code Example
The following example demonstrates how to implement the OCC loop using the
google-cloud-cpp Resource Manager library.
#include "google/cloud/resourcemanager/v3/projects_client.h"
#include "google/cloud/common_options.h"
#include "google/cloud/project.h"
#include <iostream>
#include <thread>
namespace resourcemanager = ::google::cloud::resourcemanager::v3;
namespace iam = ::google::iam::v1;
/**
* Executes an Optimistic Concurrency Control (OCC) loop to safely update an IAM policy.
*
* @param project_id The Google Cloud Project ID.
* @param role The IAM role to grant.
* @param member The member to add.
* @return StatusOr<Policy> The updated policy or the final error.
*/
google::cloud::StatusOr<iam::Policy> UpdateIamPolicyWithOCC(
std::string const& project_id,
std::string const& role,
std::string const& member) {
auto client = resourcemanager::ProjectsClient(
resourcemanager::MakeProjectsConnection());
std::string const resource_name = "projects/" + project_id;
int max_retries = 5;
int retries = 0;
// --- START OCC LOOP ---
while (retries < max_retries) {
// 1. READ: Get the current policy (includes etag)
auto policy = client.GetIamPolicy(resource_name);
if (!policy) {
return policy; // Return error immediately if Read fails (e.g. Permission Denied)
}
// 2. MODIFY: Apply changes to the local Policy object
// Note: Protobuf manipulation in C++ is explicit
bool role_found = false;
for (auto& binding : *policy->mutable_bindings()) {
if (binding.role() == role) {
binding.add_members(member);
role_found = true;
break;
}
}
if (!role_found) {
auto& new_binding = *policy->add_bindings();
new_binding.set_role(role);
new_binding.add_members(member);
}
// 3. WRITE: Attempt to set the modified policy
// The 'policy' object contains the original 'etag' from Step 1.
auto updated_policy = client.SetIamPolicy(resource_name, *policy);
// 4. CHECK STATUS
if (updated_policy) {
std::cout << "Successfully updated IAM policy.\n";
return updated_policy;
}
// 5. RETRY LOGIC
auto status = updated_policy.status();
if (status.code() == google::cloud::StatusCode::kAborted ||
status.code() == google::cloud::StatusCode::kFailedPrecondition) {
retries++;
std::cout << "Concurrency conflict (etag mismatch). Retrying... ("
<< retries << "/" << max_retries << ")\n";
// Simple exponential backoff
std::this_thread::sleep_for(std::chrono::milliseconds(100 * retries));
continue; // Restart loop
}
// If it was a different error (e.g., PermissionDenied), return it.
return updated_policy;
}
// --- END OCC LOOP ---
return google::cloud::Status(
google::cloud::StatusCode::kAborted,
"Failed to update IAM policy after max retries due to contention.");
}