Google Cloud Platform C++ Client Libraries: Optimistic Concurrency Control (OCC)

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.");
}