googleapis / googleapis/google-cloud-ruby

"Google::Cloud::Vision.image_annotator" crashes Puma app server in Rails controller, but same code runs fine as a standalone ruby script

Open
#26,251 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Ruby
Stars
1.4k
Forks
578
Avg merge
1d 11h
Merged PRs (30d)
166

Description

I've looked high and low and tried overriding the Google::Cloud::Vision module to inject debug info, but no matter what I do the app server crashes as soon as I try to instantiate a vision client with the line `Google::Cloud::Vision.image_annotator`.

I am at a loss as to why this runs fine, connects to the cloud API and outputs as expected in a standalone script, but the same code fails with no exceptions, no logging, nothing but an abrupt server crash in a Rails controller.

For auth, I am using a straightforward service key in a .json file just to get a basic API call to analyze an image successfully.

I've also tried enabling GCV logging per the instructions at https://cloud.google.com/ruby/docs/reference/google-cloud-vision-v1/latest and that did nothing that I could see. If anyone has any suggestions or ideas to workaround or get at further ways to debug this, I'd love to hear them as I'm at a hard block on this now. What am I missing?

#### Environment details

- OS: Windows 10
- Ruby version: 3.3.3-p89
- Rails 7.1.3
- Puma 6.4.2
- gem 'google-cloud-vision'

#### Steps to reproduce

1. Processing the route to DebugController#moderate (see below) crashes 100% of the time on `vision_client = Google::Cloud::Vision.image_annotator`.

#### Code examples

Standalone Ruby script (running this from console via "ruby debug_vision.rb" works and outputs two results as expected):

```ruby
# debug_vision.rb
require "google/cloud/vision"

ENV["GOOGLE_APPLICATION_CREDENTIALS"] ||= ("D:/path_to/config/credentials/google_cloud_vision.json").to_s

Google::Cloud::Vision.configure do |config|
config.credentials = ENV["GOOGLE_APPLICATION_CREDENTIALS"]
puts "=== credentials: #{config.credentials}"
end

image_path = "D:/path_to/file.jpeg"

vision = Google::Cloud::Vision.image_annotator
puts "=== vision client initialized ==="

# Open the image file as an IO object
File.open(image_path, "rb") do |image_file|
response = vision.safe_search_detection image: image_file
safe_search = response.responses.first.safe_search_annotation

puts "Adult: #{safe_search.adult}"
puts "Violence: #{safe_search.violence}"
end
```

When running via Rails controller, the credentials are set via an initializer when the server starts:
```ruby
# config/initializers/google_cloud_vision.rb
require "google/cloud/vision"
# Set the environment variable for Google Cloud credentials
ENV["GOOGLE_APPLICATION_CREDENTIALS"] ||= Rails.root.join("config/credentials/google_cloud_vision.json").to_s

Google::Cloud::Vision.configure do |config|
config.credentials = ENV["GOOGLE_APPLICATION_CREDENTIALS"]
end
```

Rails controller code (this method verifies credentials are correct and that a file is uploaded, but creating the image_annotator client object crashes the server):

```ruby
# controllers/debug_controller.rb

class DebugController < ApplicationController
# Attempted all kinds of variations of including google/cloud/vision, no differences
# require "google/cloud/vision"
# include Google::Cloud::Vision
...
def moderate
Rails.logger.debug "========== DebugController#moderate..."

uploaded_file = params[:image]

unless uploaded_file.present?
flash[:alert] = "No file was uploaded!"
render :image
end

image_path = uploaded_file.tempfile.path
Rails.logger.debug "=== image_path: #{image_path&.inspect}"

begin
# Attempted to do config within controller, no difference
# Google::Cloud::Vision.configure do |config|
# config.credentials = Rails.root.join("config/credentials/google_cloud_vision.json").to_s
# end

# Check if the environment variable is set
credentials_env = ENV["GOOGLE_APPLICATION_CREDENTIALS"]
unless credentials_env
raise "Environment variable GOOGLE_APPLICATION_CREDENTIALS not set"
end
Rails.logger.debug "========== GOOGLE_APPLICATION_CREDENTIALS: #{credentials_env} =========="

####################################################
# THE NEXT LINE CRASHES THE APP SERVER EVERY TIME #########
vision_client = Google::Cloud::Vision.image_annotator

# vision_client = VisionDebug.image_annotator # Attempt to override module, see more details
Rails.logger.debug "=== vision client initialized ==="
Rails.logger.debug "#{vision_client&.inspect}"

response = vision_client.safe_search_detection image: image_path
Rails.logger.debug "=== response: #{response&.inspect}"

safe_search = response.responses.first.safe_search_annotation
Rails.logger.debug "=== safe_search: #{safe_search&.inspect}"

render json: {
adult: safe_search.adult,
violence: safe_search.violence,
}
rescue => e
# This code block is never reached, the server just crashes
error_message = "Google Cloud Vision API call failed: #{e.message}"
Rails.logger.error error_message
flash[:alert] = error_message
render :image
end
end
```

#### Backtrace
None available, just a hard stop crash of the app server.

#### More Details
I did somewhat successfully override the Google Cloud Vision module to inject some more debugging, which further revealed it's crashing on this line in `def self.image_annotator`:
`result = service_module.const_get(:Client).new(&block)`

This is the full override code (just copied the original source and added a debug line in between each op):
```ruby
module VisionDebug
include Google::Cloud::Vision

def self.image_annotator version: :v1, transport: :grpc, &block
Rails.logger.debug "=== image_annotator, version: #{version}, transport: #{transport}"
require "google/cloud/vision/#{version.to_s.downcase}"
Rails.logger.debug "=== after require"

package_name = Google::Cloud::Vision
.constants
.select { |sym| sym.to_s.downcase == version.to_s.downcase.tr("_", "") }
.first
Rails.logger.debug "=== package_name: #{package_name}"

service_module = Google::Cloud::Vision.const_get(package_name).const_get(:ImageAnnotator)
Rails.logger.debug "=== service_module: #{service_module&.inspect}"

service_module = service_module.const_get(:Rest) if transport == :rest
Rails.logger.debug "=== service_module after rest check: #{service_module&.inspect}"

####################################################
# THE NEXT LINE CRASHES THE APP SERVER EVERY TIME #########
result = service_module.const_get(:Client).new(&block)

# result = original_image_annotator(version, transport, &block)
Rails.logger.debug "=== Result: #{result&.inspect}"
result
end
end
```

The logging output of this overridden version looks like this when called from the DebugController#moderate action:
````
Started POST "/debug/moderate" for 127.0.0.1 at 2024-06-28 15:31:17
Processing by DebugController#moderate as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "image"=>#, @content_type="image/jpeg", @original_filename="file.jpeg", @headers="Content-Disposition: form-data; name=\"image\"; filename=\"file.jpeg\"\r\nContent-Type: image/jpeg\r\n">, "commit"=>"Analyze Image"}
========== DebugController#moderate...
=== image_path: "C:/path_to/Temp/RackMultipart20240628-48008-573tm7.jpeg"
========== GOOGLE_APPLICATION_CREDENTIALS: D:/path_to/config/credentials/google_cloud_vision.json ==========
=== image_annotator, version: v1, transport: grpc
=== after require
=== package_name: V1
=== service_module: Google::Cloud::Vision::V1::ImageAnnotator
=== service_module after rest check: Google::Cloud::Vision::V1::ImageAnnotator
*** CRASH BACK TO SHELL THAT APP SERVER IS RUNNING IN ***
D:\App>
````

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.