From 6d08aa6d075e245936f680c6ef9224f3147ff998 Mon Sep 17 00:00:00 2001 From: hectorhammett Date: Wed, 28 Jan 2026 00:55:20 +0000 Subject: [PATCH 1/2] feat: Add support for the header id feature --- .../lib/google/cloud/spanner.rb | 8 +- .../lib/google/cloud/spanner/errors.rb | 1 + .../cloud/spanner/request_id_interceptor.rb | 137 ++++++++++++ .../lib/google/cloud/spanner/service.rb | 9 +- .../lib/google/cloud/spanner/spanner_error.rb | 34 +++ .../test/google/cloud/spanner_test.rb | 203 ++++++++++++++++++ 6 files changed, 389 insertions(+), 3 deletions(-) create mode 100644 google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb create mode 100644 google-cloud-spanner/lib/google/cloud/spanner/spanner_error.rb diff --git a/google-cloud-spanner/lib/google/cloud/spanner.rb b/google-cloud-spanner/lib/google/cloud/spanner.rb index dc04208b..18a72485 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner.rb @@ -15,6 +15,8 @@ require "google-cloud-spanner" require "google/cloud/spanner/project" +require "google/cloud/spanner/spanner_error" +require "google/cloud/spanner/request_id_interceptor" require "google/cloud/config" require "google/cloud/env" @@ -96,7 +98,7 @@ module Spanner def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, endpoint: nil, project: nil, keyfile: nil, emulator_host: nil, lib_name: nil, lib_version: nil, - enable_leader_aware_routing: true, universe_domain: nil + enable_leader_aware_routing: true, universe_domain: nil, process_id: nil project_id ||= project || default_project_id scope ||= configure.scope timeout ||= configure.timeout @@ -105,6 +107,7 @@ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, credentials ||= keyfile lib_name ||= configure.lib_name lib_version ||= configure.lib_version + interceptors = [RequestIdInterceptor.new(process_id: process_id)] universe_domain ||= configure.universe_domain if emulator_host @@ -127,7 +130,8 @@ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, Spanner::Service.new( project_id, credentials, quota_project: configure.quota_project, host: endpoint, timeout: timeout, lib_name: lib_name, - lib_version: lib_version, universe_domain: universe_domain, + lib_version: lib_version, interceptors: interceptors, + universe_domain: universe_domain, enable_leader_aware_routing: enable_leader_aware_routing ), query_options: configure.query_options diff --git a/google-cloud-spanner/lib/google/cloud/spanner/errors.rb b/google-cloud-spanner/lib/google/cloud/spanner/errors.rb index 5f0c4643..e8856cf7 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/errors.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/errors.rb @@ -14,6 +14,7 @@ require "google/cloud/errors" +require "google/cloud/spanner/spanner_error" module Google module Cloud diff --git a/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb b/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb new file mode 100644 index 00000000..574beb74 --- /dev/null +++ b/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb @@ -0,0 +1,137 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "grpc" +require "securerandom" +require "mutex_m" +require "google/cloud/spanner/errors" + +module Google + module Cloud + module Spanner + class RequestIdInterceptor < GRPC::ClientInterceptor + @client_id_counter = 0 + @client_mutex = Mutex.new + @channel_id_counter = 0 + @channel_mutex = Mutex.new + @request_id_counter = 0 + @request_id_mutex = Mutex.new + @process_id = nil; + @process_id_mutex = Mutex.new + + def self.next_client_id + @client_mutex.synchronize do + @client_id_counter += 1 + end + end + + def self.next_channel_id + @channel_mutex.synchronize do + @channel_id_counter += 1 + end + end + + def self.get_process_id process_id = nil + @process_id_mutex.synchronize do + if process_id.nil? || !@process_id.nil? + return @process_id ||= SecureRandom.hex(8) + end + + case process_id + when Integer + if process_id >= 0 && process_id.bit_length <= 64 + return process_id.to_s(16).rjust(16,'0') + end + when String + if (process_id =~ /\A[0-9a-fA-F]{16}\z/) + return process_id + end + end + + raise ArgumentError,'process_id must be a 64-bit integer or 16-character hex string' + end + end + + def initialize process_id: nil + @version = 1 + @process_id = self.class.get_process_id process_id + @client_id = self.class.next_client_id + @channel_id = self.class.next_channel_id + @request_id_counter = 0 + @request_mutex = Mutex.new + end + + def request_response method:, request:, call:, metadata: + update_metadata_for_call call, metadata do + yield + end + end + + def client_streamer method:, request:, call:, metadata: + update_metadata_for_call call, metadata do + yield + end + end + + def server_streamer method:, request:, call:, metadata: + update_metadata_for_call call, metadata do + yield + end + end + + def bidi_streamer method:, request:, call:, metadata: + update_metadata_for_call call, metadata do + yield + end + end + + private + + def validate_process_id process_id + value.is_a?(Integer) && value >=0 && value.bit_length <= 64 + end + + def update_metadata_for_call call, metadata + request_id = nil + attempt = 1; + + if metadata.include? :"x-goog-spanner-request-id" + request_id, attempt = get_header_info metadata[:"x-goog-spanner-request-id"] + else + request_id = @request_mutex.synchronize { @request_id_counter += 1 } + end + + formatted_request_id = format_request_id request_id, attempt + metadata[:"x-goog-spanner-request-id"] = formatted_request_id + + response = yield + response + rescue => ex + ex.instance_variable_set :@spanner_header_id, formatted_request_id + raise ex + end + + def format_request_id request_id, attempt + "#{@version}.#{@process_id}.#{@client_id}.#{@channel_id}.#{request_id}.#{attempt}" + end + + def get_header_info header + version, process_id, client_id, channel_id, request_id, attempt = header.split('.') + [request_id, attempt.to_i + 1]; + end + end + end + end +end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/service.rb b/google-cloud-spanner/lib/google/cloud/spanner/service.rb index 5716984f..ba8ddadf 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/service.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/service.rb @@ -36,6 +36,7 @@ class Service attr_accessor :lib_name attr_accessor :lib_version attr_accessor :quota_project + attr_accessor :interceptors attr_accessor :enable_leader_aware_routing attr_reader :universe_domain @@ -51,13 +52,15 @@ class Service # @param timeout [::Numeric, nil] Optional. Timeout for Gapic client. # @param lib_name [::String, nil] Optional. Library name for headers. # @param lib_version [::String, nil] Optional. Library version for headers. + # @param interceptors [::Array, nil] Optional. + # An array of interceptors that are run before calls are executed. # @param enable_leader_aware_routing [::Boolean, nil] Optional. Whether Leader # Aware Routing should be enabled. # @param universe_domain [::String, nil] Optional. The domain of the universe to connect to. # @private def initialize project, credentials, quota_project: nil, host: nil, timeout: nil, lib_name: nil, lib_version: nil, - enable_leader_aware_routing: nil, universe_domain: nil + interceptors: nil, enable_leader_aware_routing: nil, universe_domain: nil @project = project @credentials = credentials @quota_project = quota_project || (credentials.quota_project_id if credentials.respond_to? :quota_project_id) @@ -73,6 +76,7 @@ def initialize project, credentials, quota_project: nil, @timeout = timeout @lib_name = lib_name @lib_version = lib_version + @interceptors = interceptors @enable_leader_aware_routing = enable_leader_aware_routing end @@ -106,6 +110,7 @@ def service config.lib_name = lib_name_with_prefix config.lib_version = Google::Cloud::Spanner::VERSION config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" } + config.interceptors = @interceptors if @interceptors end end attr_accessor :mocked_service @@ -122,6 +127,7 @@ def instances config.lib_name = lib_name_with_prefix config.lib_version = Google::Cloud::Spanner::VERSION config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" } + config.interceptors = @interceptors if @interceptors end end attr_accessor :mocked_instances @@ -138,6 +144,7 @@ def databases config.lib_name = lib_name_with_prefix config.lib_version = Google::Cloud::Spanner::VERSION config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" } + config.interceptors = @interceptors if @interceptors end end attr_accessor :mocked_databases diff --git a/google-cloud-spanner/lib/google/cloud/spanner/spanner_error.rb b/google-cloud-spanner/lib/google/cloud/spanner/spanner_error.rb new file mode 100644 index 00000000..d29d8379 --- /dev/null +++ b/google-cloud-spanner/lib/google/cloud/spanner/spanner_error.rb @@ -0,0 +1,34 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "google/cloud/errors" + +# This is a monkey patch for Google::Cloud::Error to add support for the request_id method +# to keep this spanner exclusive method inside the spanner code. +module Google + module Cloud + class Error + ## + # The Spanner header ID if there was an error on the request. + # + # @return [String, nil] + # + def request_id + return nil unless cause.instance_variable_defined? :@spanner_header_id + cause.instance_variable_get :@spanner_header_id + end + end + end +end diff --git a/google-cloud-spanner/test/google/cloud/spanner_test.rb b/google-cloud-spanner/test/google/cloud/spanner_test.rb index be5df902..1275fb85 100644 --- a/google-cloud-spanner/test/google/cloud/spanner_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner_test.rb @@ -563,6 +563,209 @@ def quota_project_credentials.is_a? target end end end + + it "adds the request_id_interceptor and increments the client_id" do + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@client_id_counter, 0 + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@channel_id_counter, 0 + + # Clear all environment variables + ENV.stub :[], nil do + # Get project_id from Google Compute Engine + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + spanner1 = Google::Cloud::Spanner.new + interceptors1 = spanner1.service.instance_variable_get :@interceptors + _(interceptors1.length).must_equal 1 + _(interceptors1.first).must_be_kind_of Google::Cloud::Spanner::RequestIdInterceptor + _(interceptors1.first.instance_variable_get(:@client_id)).must_equal 1 + _(interceptors1.first.instance_variable_get(:@channel_id)).must_equal 1 + process_id1 = interceptors1.first.instance_variable_get(:@process_id) + _(process_id1).must_match /^[0-9a-f]+$/ + + spanner2 = Google::Cloud::Spanner.new + interceptors2 = spanner2.service.instance_variable_get :@interceptors + _(interceptors2.length).must_equal 1 + _(interceptors2.first).must_be_kind_of Google::Cloud::Spanner::RequestIdInterceptor + _(interceptors2.first.instance_variable_get(:@client_id)).must_equal 2 + _(interceptors2.first.instance_variable_get(:@channel_id)).must_equal 2 + process_id2 = interceptors2.first.instance_variable_get(:@process_id) + _(process_id1).must_equal process_id2 + end + end + end + end + + it "adds the request_id_interceptor and uses the provided process_id" do + # Set this to Nil as the previous tests set the class variable and avoids us setting a new value as per design. + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@process_id, nil + # Clear all environment variables + ENV.stub :[], nil do + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + # Test with Integer process_id + spanner_int = Google::Cloud::Spanner.new project_id: "project-id", credentials: default_credentials, process_id: 123 + interceptor_int = spanner_int.service.instance_variable_get(:@interceptors).first + process_id_int = interceptor_int.instance_variable_get(:@process_id) + _(process_id_int).must_equal "000000000000007b" + + # Test with String hex process_id + custom_hex = "abcdef0123456789" + spanner_hex = Google::Cloud::Spanner.new project_id: "project-id", credentials: default_credentials, process_id: custom_hex + interceptor_hex = spanner_hex.service.instance_variable_get(:@interceptors).first + process_id_hex = interceptor_hex.instance_variable_get(:@process_id) + _(process_id_hex).must_equal custom_hex + end + end + end + end + + it "raises ArgumentError for invalid process_id values" do + # Set this to Nil as the previous tests set the class variable and avoids us setting a new value as per design. + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@process_id, nil + # Clear all environment variables + ENV.stub :[], nil do + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + # Test with invalid Integer process_id (out of range) + assert_raises ArgumentError do + Google::Cloud::Spanner.new project_id: "project-id", credentials: default_credentials, process_id: -1 + end + assert_raises ArgumentError do + Google::Cloud::Spanner.new project_id: "project-id", credentials: default_credentials, process_id: (2**64) + end + + # Test with invalid String process_id (not hex or wrong length) + assert_raises ArgumentError do + Google::Cloud::Spanner.new project_id: "project-id", credentials: default_credentials, process_id: "not-hex" + end + assert_raises ArgumentError do + Google::Cloud::Spanner.new project_id: "project-id", credentials: default_credentials, process_id: "abc" + end + end + end + end + end + + it "adds the request_id_interceptor and increments the attempt_id on retries" do + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@client_id_counter, 0 + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@channel_id_counter, 0 + + # Clear all environment variables + ENV.stub :[], nil do + # Get project_id from Google Compute Engine + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + spanner = Google::Cloud::Spanner.new + interceptor = spanner.service.instance_variable_get(:@interceptors).first + + mock_call = OpenStruct.new + mock_metadata = {} + + # First call + interceptor.request_response(method: :m, request: :r, call: mock_call, metadata: mock_metadata) { nil } + first_request_id = mock_metadata[:"x-goog-spanner-request-id"] + _(first_request_id).must_match /^1\.[0-9a-f]+\.1\.1\.1\.1$/ + + # Second call (simulating a retry for the same logical request) + interceptor.request_response(method: :m, request: :r, call: mock_call, metadata: mock_metadata) { nil } + second_request_id = mock_metadata[:"x-goog-spanner-request-id"] + _(second_request_id).must_match /^1\.[0-9a-f]+\.1\.1\.1\.2$/ + _(second_request_id.split(".")[0..-2]).must_equal first_request_id.split(".")[0..-2] + + # Third call (another retry) + interceptor.request_response(method: :m, request: :r, call: mock_call, metadata: mock_metadata) { nil } + third_request_id = mock_metadata[:"x-goog-spanner-request-id"] + _(third_request_id).must_match /^1\.[0-9a-f]+\.1\.1\.1\.3$/ + _(third_request_id.split(".")[0..-2]).must_equal first_request_id.split(".")[0..-2] + + # Verify that the ID is attached to the exception + err = assert_raises GRPC::Unavailable do + interceptor.request_response(method: :m, request: :r, call: mock_call, metadata: mock_metadata) do + raise GRPC::Unavailable.new "test error" + end + end + fourth_request_id = mock_metadata[:"x-goog-spanner-request-id"] + _(fourth_request_id).must_match /\.4$/ + _(err.instance_variable_get(:@spanner_header_id)).must_equal fourth_request_id + + # Verify that our Google::Cloud::Error extension can read it + g_error = Google::Cloud::Error.new "wrapped error" + g_error.stub :cause, err do + _(g_error.request_id).must_equal fourth_request_id + end + end + end + end + end + + it "retrieves the request_id from a Google::Cloud::Error after a failed request" do + ENV.stub :[], nil do + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + spanner = Google::Cloud.spanner + interceptor = spanner.service.instance_variable_get(:@interceptors).first + + mock_call = OpenStruct.new + mock_metadata = {} + + # Simulate a request that raises a gRPC error + grpc_err = assert_raises GRPC::Unavailable do + interceptor.request_response(method: :m, request: :r, call: mock_call, metadata: mock_metadata) do + raise GRPC::Unavailable.new "transient failure" + end + end + + header_id = mock_metadata[:"x-goog-spanner-request-id"] + + # Verify that our Google::Cloud::Error extension can read it from the cause + cloud_err = Google::Cloud::Error.new "wrapped error" + cloud_err.stub :cause, grpc_err do + _(cloud_err.request_id).must_equal header_id + end + end + end + end + end + + it "sends the x-goog-spanner-request-id header in the metadata" do + # Reset class-level state for isolation + Google::Cloud::Spanner::RequestIdInterceptor.instance_variable_set :@process_id, nil + + # Create a robust mock credential that satisfies chan_creds + mock_creds = OpenStruct.new( + client: OpenStruct.new(updater_proc: ->(m) { m }), + quota_project_id: "test-project" + ) + def mock_creds.is_a? target; target == Google::Auth::Credentials; end + + # This recorder will capture the metadata at the end of the interceptor chain + captured_metadata = nil + recorder = Class.new(GRPC::ClientInterceptor) do + define_method(:request_response) do |**kwargs| + captured_metadata = kwargs[:metadata] + raise GRPC::PermissionDenied.new "stop" + end + end.new + + ENV.stub :[], nil do + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, mock_creds do + spanner = Google::Cloud.spanner + # Prepend the recorder so it runs deepest in the LIFO stack + spanner.service.interceptors.unshift recorder + + begin + spanner.instances + rescue Google::Cloud::Error + # Expected error from recorder + end + + # Verify that the RequestIdInterceptor added the header to the metadata + _(captured_metadata).wont_be :nil? + end + end + end + end end describe "Spanner.configure" do From 29389ed63ebc146f0394f4235a9d6a4ddd4c99f3 Mon Sep 17 00:00:00 2001 From: hectorhammett Date: Wed, 28 Jan 2026 22:20:38 +0000 Subject: [PATCH 2/2] Add the mutex gem dependency --- google-cloud-spanner/Gemfile | 1 + .../lib/google/cloud/spanner.rb | 2 +- .../cloud/spanner/request_id_interceptor.rb | 72 ++++++++++--------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/google-cloud-spanner/Gemfile b/google-cloud-spanner/Gemfile index b944eb10..5ad8faf3 100644 --- a/google-cloud-spanner/Gemfile +++ b/google-cloud-spanner/Gemfile @@ -16,6 +16,7 @@ gem "minitest", "~> 5.25" gem "minitest-autotest", "~> 1.0" gem "minitest-focus", "~> 1.4" gem "minitest-rg", "~> 5.3" +gem "mutex_m", "~> 0.3.0" gem "pry", group: :development, require: false gem "rake" gem "redcarpet", "~> 3.0" diff --git a/google-cloud-spanner/lib/google/cloud/spanner.rb b/google-cloud-spanner/lib/google/cloud/spanner.rb index 18a72485..80806fef 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner.rb @@ -107,7 +107,7 @@ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, credentials ||= keyfile lib_name ||= configure.lib_name lib_version ||= configure.lib_version - interceptors = [RequestIdInterceptor.new(process_id: process_id)] + interceptors = [RequestIdInterceptor.new(process_id: process_id)] universe_domain ||= configure.universe_domain if emulator_host diff --git a/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb b/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb index 574beb74..54443ab0 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/request_id_interceptor.rb @@ -28,7 +28,7 @@ class RequestIdInterceptor < GRPC::ClientInterceptor @channel_mutex = Mutex.new @request_id_counter = 0 @request_id_mutex = Mutex.new - @process_id = nil; + @process_id = nil @process_id_mutex = Mutex.new def self.next_client_id @@ -46,25 +46,26 @@ def self.next_channel_id def self.get_process_id process_id = nil @process_id_mutex.synchronize do if process_id.nil? || !@process_id.nil? - return @process_id ||= SecureRandom.hex(8) + return @process_id ||= (SecureRandom.hex 8) end case process_id when Integer if process_id >= 0 && process_id.bit_length <= 64 - return process_id.to_s(16).rjust(16,'0') + return process_id.to_s(16).rjust(16, "0") end when String - if (process_id =~ /\A[0-9a-fA-F]{16}\z/) + if process_id =~ /\A[0-9a-fA-F]{16}\z/ return process_id end end - raise ArgumentError,'process_id must be a 64-bit integer or 16-character hex string' + raise ArgumentError, "process_id must be a 64-bit integer or 16-character hex string" end end def initialize process_id: nil + super @version = 1 @process_id = self.class.get_process_id process_id @client_id = self.class.next_client_id @@ -73,39 +74,43 @@ def initialize process_id: nil @request_mutex = Mutex.new end - def request_response method:, request:, call:, metadata: - update_metadata_for_call call, metadata do - yield - end + def request_response method:, request:, call:, metadata:, &block + # Unused. This is to avoid Rubocop's Lint/UnusedMethodArgument + _method = method + _request = request + _call = call + update_metadata_for_call metadata, &block end - def client_streamer method:, request:, call:, metadata: - update_metadata_for_call call, metadata do - yield - end + def client_streamer method:, request:, call:, metadata:, &block + # Unused. This is to avoid Rubocop's Lint/UnusedMethodArgument + _method = method + _request = request + _call = call + update_metadata_for_call metadata, &block end - def server_streamer method:, request:, call:, metadata: - update_metadata_for_call call, metadata do - yield - end + def server_streamer method:, request:, call:, metadata:, &block + # Unused. This is to avoid Rubocop's Lint/UnusedMethodArgument + _method = method + _request = request + _call = call + update_metadata_for_call metadata, &block end - def bidi_streamer method:, request:, call:, metadata: - update_metadata_for_call call, metadata do - yield - end + def bidi_streamer method:, request:, call:, metadata:, &block + # Unused. This is to avoid Rubocop's Lint/UnusedMethodArgument + _method = method + _request = request + _call = call + update_metadata_for_call metadata, &block end private - def validate_process_id process_id - value.is_a?(Integer) && value >=0 && value.bit_length <= 64 - end - - def update_metadata_for_call call, metadata + def update_metadata_for_call metadata request_id = nil - attempt = 1; + attempt = 1 if metadata.include? :"x-goog-spanner-request-id" request_id, attempt = get_header_info metadata[:"x-goog-spanner-request-id"] @@ -116,11 +121,10 @@ def update_metadata_for_call call, metadata formatted_request_id = format_request_id request_id, attempt metadata[:"x-goog-spanner-request-id"] = formatted_request_id - response = yield - response - rescue => ex - ex.instance_variable_set :@spanner_header_id, formatted_request_id - raise ex + yield + rescue StandardError => e + e.instance_variable_set :@spanner_header_id, formatted_request_id + raise e end def format_request_id request_id, attempt @@ -128,8 +132,8 @@ def format_request_id request_id, attempt end def get_header_info header - version, process_id, client_id, channel_id, request_id, attempt = header.split('.') - [request_id, attempt.to_i + 1]; + _, _, _, _, request_id, attempt = header.split "." + [request_id, attempt.to_i + 1] end end end