Skip to content

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

@sampenguin

Description

@sampenguin

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):

# 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:

# 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):

# 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):

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"=>#<ActionDispatch::Http::UploadedFile:0x000001f0bbc2aaf8 @tempfile=#<Tempfile:C:/path_to/file.jpeg>, @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>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions