semian 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +235 -0
- data/LICENSE.md +21 -0
- data/README.md +836 -0
- data/ext/semian/extconf.rb +21 -19
- data/lib/semian/adapter.rb +8 -4
- data/lib/semian/circuit_breaker.rb +16 -10
- data/lib/semian/grpc.rb +32 -10
- data/lib/semian/instrumentable.rb +2 -0
- data/lib/semian/lru_hash.rb +15 -14
- data/lib/semian/mysql2.rb +13 -9
- data/lib/semian/net_http.rb +10 -4
- data/lib/semian/platform.rb +3 -1
- data/lib/semian/protected_resource.rb +5 -3
- data/lib/semian/rails.rb +12 -6
- data/lib/semian/redis.rb +15 -13
- data/lib/semian/redis_client.rb +5 -3
- data/lib/semian/resource.rb +5 -3
- data/lib/semian/simple_integer.rb +4 -2
- data/lib/semian/simple_sliding_window.rb +5 -3
- data/lib/semian/simple_state.rb +3 -1
- data/lib/semian/unprotected_resource.rb +2 -0
- data/lib/semian/version.rb +3 -1
- data/lib/semian.rb +61 -45
- metadata +11 -201
    
        data/ext/semian/extconf.rb
    CHANGED
    
    | @@ -1,33 +1,35 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            $LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require "semian/platform"
         | 
| 4 6 |  | 
| 5 7 | 
             
            unless Semian.sysv_semaphores_supported?
         | 
| 6 | 
            -
              File.write | 
| 7 | 
            -
            all:
         | 
| 8 | 
            -
            clean:
         | 
| 9 | 
            -
            install:
         | 
| 10 | 
            -
            MAKEFILE
         | 
| 8 | 
            +
              File.write("Makefile", <<~MAKEFILE)
         | 
| 9 | 
            +
                all:
         | 
| 10 | 
            +
                clean:
         | 
| 11 | 
            +
                install:
         | 
| 12 | 
            +
              MAKEFILE
         | 
| 11 13 | 
             
              exit
         | 
| 12 14 | 
             
            end
         | 
| 13 15 |  | 
| 14 | 
            -
            require  | 
| 16 | 
            +
            require "mkmf"
         | 
| 15 17 |  | 
| 16 | 
            -
            abort  | 
| 17 | 
            -
            abort  | 
| 18 | 
            +
            abort "openssl is missing. please install openssl." unless find_header("openssl/sha.h")
         | 
| 19 | 
            +
            abort "openssl is missing. please install openssl." unless find_library("crypto", "SHA1")
         | 
| 18 20 |  | 
| 19 | 
            -
            have_header  | 
| 20 | 
            -
            have_header  | 
| 21 | 
            -
            have_header  | 
| 21 | 
            +
            have_header "sys/ipc.h"
         | 
| 22 | 
            +
            have_header "sys/sem.h"
         | 
| 23 | 
            +
            have_header "sys/types.h"
         | 
| 22 24 |  | 
| 23 | 
            -
            have_func  | 
| 24 | 
            -
            have_func  | 
| 25 | 
            +
            have_func "rb_thread_blocking_region"
         | 
| 26 | 
            +
            have_func "rb_thread_call_without_gvl"
         | 
| 25 27 |  | 
| 26 28 | 
             
            $CFLAGS = "-D_GNU_SOURCE -Werror -Wall "
         | 
| 27 | 
            -
            if ENV.key?( | 
| 28 | 
            -
               | 
| 29 | 
            +
            $CFLAGS += if ENV.key?("DEBUG")
         | 
| 30 | 
            +
              "-O0 -g -DDEBUG"
         | 
| 29 31 | 
             
            else
         | 
| 30 | 
            -
               | 
| 32 | 
            +
              "-O3"
         | 
| 31 33 | 
             
            end
         | 
| 32 34 |  | 
| 33 | 
            -
            create_makefile( | 
| 35 | 
            +
            create_makefile("semian/semian")
         | 
    
        data/lib/semian/adapter.rb
    CHANGED
    
    | @@ -1,9 +1,11 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "semian"
         | 
| 2 4 |  | 
| 3 5 | 
             
            module Semian
         | 
| 4 6 | 
             
              module Adapter
         | 
| 5 7 | 
             
                def semian_identifier
         | 
| 6 | 
            -
                  raise NotImplementedError | 
| 8 | 
            +
                  raise NotImplementedError, "Semian adapters must implement a `semian_identifier` method"
         | 
| 7 9 | 
             
                end
         | 
| 8 10 |  | 
| 9 11 | 
             
                def semian_resource
         | 
| @@ -31,6 +33,7 @@ module Semian | |
| 31 33 |  | 
| 32 34 | 
             
                def acquire_semian_resource(scope:, adapter:, &block)
         | 
| 33 35 | 
             
                  return yield if resource_already_acquired?
         | 
| 36 | 
            +
             | 
| 34 37 | 
             
                  semian_resource.acquire(scope: scope, adapter: adapter, resource: self) do
         | 
| 35 38 | 
             
                    mark_resource_as_acquired(&block)
         | 
| 36 39 | 
             
                  end
         | 
| @@ -48,16 +51,17 @@ module Semian | |
| 48 51 |  | 
| 49 52 | 
             
                def semian_options
         | 
| 50 53 | 
             
                  return @semian_options if defined? @semian_options
         | 
| 54 | 
            +
             | 
| 51 55 | 
             
                  options = raw_semian_options
         | 
| 52 56 | 
             
                  @semian_options = options && options.map { |k, v| [k.to_sym, v] }.to_h
         | 
| 53 57 | 
             
                end
         | 
| 54 58 |  | 
| 55 59 | 
             
                def raw_semian_options
         | 
| 56 | 
            -
                  raise NotImplementedError | 
| 60 | 
            +
                  raise NotImplementedError, "Semian adapters must implement a `raw_semian_options` method"
         | 
| 57 61 | 
             
                end
         | 
| 58 62 |  | 
| 59 63 | 
             
                def resource_exceptions
         | 
| 60 | 
            -
                  raise NotImplementedError | 
| 64 | 
            +
                  raise NotImplementedError, "Semian adapters must implement a `resource_exceptions` method"
         | 
| 61 65 | 
             
                end
         | 
| 62 66 |  | 
| 63 67 | 
             
                def resource_already_acquired?
         | 
| @@ -1,5 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Semian
         | 
| 2 | 
            -
              class CircuitBreaker  | 
| 4 | 
            +
              class CircuitBreaker # :nodoc:
         | 
| 3 5 | 
             
                extend Forwardable
         | 
| 4 6 |  | 
| 5 7 | 
             
                def_delegators :@state, :closed?, :open?, :half_open?
         | 
| @@ -7,7 +9,9 @@ module Semian | |
| 7 9 | 
             
                attr_reader :name, :half_open_resource_timeout, :error_timeout, :state, :last_error
         | 
| 8 10 |  | 
| 9 11 | 
             
                def initialize(name, exceptions:, success_threshold:, error_threshold:,
         | 
| 10 | 
            -
             | 
| 12 | 
            +
                  error_timeout:, implementation:, half_open_resource_timeout: nil,
         | 
| 13 | 
            +
                  error_threshold_timeout: nil)
         | 
| 14 | 
            +
             | 
| 11 15 | 
             
                  @name = name.to_sym
         | 
| 12 16 | 
             
                  @success_count_threshold = success_threshold
         | 
| 13 17 | 
             
                  @error_count_threshold = error_threshold
         | 
| @@ -25,6 +29,7 @@ module Semian | |
| 25 29 |  | 
| 26 30 | 
             
                def acquire(resource = nil, &block)
         | 
| 27 31 | 
             
                  return yield if disabled?
         | 
| 32 | 
            +
             | 
| 28 33 | 
             
                  transition_to_half_open if transition_to_half_open?
         | 
| 29 34 |  | 
| 30 35 | 
             
                  raise OpenCircuitError unless request_allowed?
         | 
| @@ -63,6 +68,7 @@ module Semian | |
| 63 68 |  | 
| 64 69 | 
             
                def mark_success
         | 
| 65 70 | 
             
                  return unless half_open?
         | 
| 71 | 
            +
             | 
| 66 72 | 
             
                  @successes.increment
         | 
| 67 73 | 
             
                  transition_to_close if success_threshold_reached?
         | 
| 68 74 | 
             
                end
         | 
| @@ -80,8 +86,7 @@ module Semian | |
| 80 86 | 
             
                end
         | 
| 81 87 |  | 
| 82 88 | 
             
                def in_use?
         | 
| 83 | 
            -
                   | 
| 84 | 
            -
                  @errors.size > 0
         | 
| 89 | 
            +
                  !error_timeout_expired? && !@errors.empty?
         | 
| 85 90 | 
             
                end
         | 
| 86 91 |  | 
| 87 92 | 
             
                private
         | 
| @@ -117,6 +122,7 @@ module Semian | |
| 117 122 | 
             
                def error_timeout_expired?
         | 
| 118 123 | 
             
                  last_error_time = @errors.last
         | 
| 119 124 | 
             
                  return false unless last_error_time
         | 
| 125 | 
            +
             | 
| 120 126 | 
             
                  Time.at(last_error_time) + @error_timeout < Time.now
         | 
| 121 127 | 
             
                end
         | 
| 122 128 |  | 
| @@ -133,12 +139,12 @@ module Semian | |
| 133 139 | 
             
                  return if @state.nil? || new_state == @state.value
         | 
| 134 140 |  | 
| 135 141 | 
             
                  str = "[#{self.class.name}] State transition from #{@state.value} to #{new_state}."
         | 
| 136 | 
            -
                  str  | 
| 137 | 
            -
                  str  | 
| 138 | 
            -
                  str  | 
| 139 | 
            -
                  str  | 
| 142 | 
            +
                  str += " success_count=#{@successes.value} error_count=#{@errors.size}"
         | 
| 143 | 
            +
                  str += " success_count_threshold=#{@success_count_threshold} error_count_threshold=#{@error_count_threshold}"
         | 
| 144 | 
            +
                  str += " error_timeout=#{@error_timeout} error_last_at=\"#{@errors.last}\""
         | 
| 145 | 
            +
                  str += " name=\"#{@name}\""
         | 
| 140 146 | 
             
                  if new_state == :open && @last_error
         | 
| 141 | 
            -
                    str  | 
| 147 | 
            +
                    str += " last_error_message=#{@last_error.message.inspect}"
         | 
| 142 148 | 
             
                  end
         | 
| 143 149 |  | 
| 144 150 | 
             
                  Semian.logger.info(str)
         | 
| @@ -149,7 +155,7 @@ module Semian | |
| 149 155 | 
             
                end
         | 
| 150 156 |  | 
| 151 157 | 
             
                def disabled?
         | 
| 152 | 
            -
                  ENV[ | 
| 158 | 
            +
                  ENV["SEMIAN_CIRCUIT_BREAKER_DISABLED"] || ENV["SEMIAN_DISABLED"]
         | 
| 153 159 | 
             
                end
         | 
| 154 160 |  | 
| 155 161 | 
             
                def maybe_with_half_open_resource_timeout(resource, &block)
         | 
    
        data/lib/semian/grpc.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "semian/adapter"
         | 
| 4 | 
            +
            require "grpc"
         | 
| 3 5 |  | 
| 4 6 | 
             
            module GRPC
         | 
| 5 7 | 
             
              GRPC::Unavailable.include(::Semian::AdapterError)
         | 
| @@ -22,7 +24,6 @@ end | |
| 22 24 |  | 
| 23 25 | 
             
            module Semian
         | 
| 24 26 | 
             
              module GRPC
         | 
| 25 | 
            -
                attr_reader :raw_semian_options
         | 
| 26 27 | 
             
                include Semian::Adapter
         | 
| 27 28 |  | 
| 28 29 | 
             
                ResourceBusyError = ::GRPC::ResourceBusyError
         | 
| @@ -40,6 +41,7 @@ module Semian | |
| 40 41 |  | 
| 41 42 | 
             
                  def semian_configuration=(configuration)
         | 
| 42 43 | 
             
                    raise Semian::GRPC::SemianConfigurationChangedError unless @semian_configuration.nil?
         | 
| 44 | 
            +
             | 
| 43 45 | 
             
                    @semian_configuration = configuration
         | 
| 44 46 | 
             
                  end
         | 
| 45 47 |  | 
| @@ -52,10 +54,10 @@ module Semian | |
| 52 54 | 
             
                  @raw_semian_options ||= begin
         | 
| 53 55 | 
             
                    # If the host is empty, it's possible that the adapter was initialized
         | 
| 54 56 | 
             
                    # with the channel. Therefore, we look into the channel to find the host
         | 
| 55 | 
            -
                    if @host.empty?
         | 
| 56 | 
            -
                       | 
| 57 | 
            +
                    host = if @host.empty?
         | 
| 58 | 
            +
                      @ch.target
         | 
| 57 59 | 
             
                    else
         | 
| 58 | 
            -
                       | 
| 60 | 
            +
                      @host
         | 
| 59 61 | 
             
                    end
         | 
| 60 62 | 
             
                    @raw_semian_options = Semian::GRPC.retrieve_semian_configuration(host)
         | 
| 61 63 | 
             
                    @raw_semian_options = @raw_semian_options.dup unless @raw_semian_options.nil?
         | 
| @@ -81,22 +83,42 @@ module Semian | |
| 81 83 |  | 
| 82 84 | 
             
                def request_response(*, **)
         | 
| 83 85 | 
             
                  return super if disabled?
         | 
| 84 | 
            -
             | 
| 86 | 
            +
             | 
| 87 | 
            +
                  acquire_semian_resource_grpc(scope: :request_response) { super }
         | 
| 85 88 | 
             
                end
         | 
| 86 89 |  | 
| 87 90 | 
             
                def client_streamer(*, **)
         | 
| 88 91 | 
             
                  return super if disabled?
         | 
| 89 | 
            -
             | 
| 92 | 
            +
             | 
| 93 | 
            +
                  acquire_semian_resource_grpc(scope: :client_streamer) { super }
         | 
| 90 94 | 
             
                end
         | 
| 91 95 |  | 
| 92 96 | 
             
                def server_streamer(*, **)
         | 
| 93 97 | 
             
                  return super if disabled?
         | 
| 94 | 
            -
             | 
| 98 | 
            +
             | 
| 99 | 
            +
                  acquire_semian_resource_grpc(scope: :server_streamer) { super }
         | 
| 95 100 | 
             
                end
         | 
| 96 101 |  | 
| 97 102 | 
             
                def bidi_streamer(*, **)
         | 
| 98 103 | 
             
                  return super if disabled?
         | 
| 99 | 
            -
             | 
| 104 | 
            +
             | 
| 105 | 
            +
                  acquire_semian_resource_grpc(scope: :bidi_streamer) { super }
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def acquire_semian_resource_grpc(scope:)
         | 
| 109 | 
            +
                  acquire_semian_resource(adapter: :grpc, scope: scope) do
         | 
| 110 | 
            +
                    result = yield
         | 
| 111 | 
            +
                    handle_operation(result, scope) if result.is_a?(::GRPC::ActiveCall::Operation)
         | 
| 112 | 
            +
                    result
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def handle_operation(operation, scope)
         | 
| 117 | 
            +
                  execute = operation.singleton_method(:execute)
         | 
| 118 | 
            +
                  operation.instance_variable_set(:@semian, self)
         | 
| 119 | 
            +
                  operation.define_singleton_method(:execute) do
         | 
| 120 | 
            +
                    @semian.send(:acquire_semian_resource, **{ adapter: :grpc, scope: scope }) { execute.call }
         | 
| 121 | 
            +
                  end
         | 
| 100 122 | 
             
                end
         | 
| 101 123 | 
             
              end
         | 
| 102 124 | 
             
            end
         | 
    
        data/lib/semian/lru_hash.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            class LRUHash
         | 
| 2 4 | 
             
              # This LRU (Least Recently Used) hash will allow
         | 
| 3 5 | 
             
              # the cleaning of resources as time goes on.
         | 
| @@ -120,10 +122,10 @@ class LRUHash | |
| 120 122 |  | 
| 121 123 | 
             
              def clear_unused_resources
         | 
| 122 124 | 
             
                payload = {
         | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 125 | 
            +
                  size: @table.size,
         | 
| 126 | 
            +
                  examined: 0,
         | 
| 127 | 
            +
                  cleared: 0,
         | 
| 128 | 
            +
                  elapsed: nil,
         | 
| 127 129 | 
             
                }
         | 
| 128 130 | 
             
                timer_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
         | 
| 129 131 |  | 
| @@ -155,20 +157,19 @@ class LRUHash | |
| 155 157 | 
             
                end
         | 
| 156 158 | 
             
              end
         | 
| 157 159 |  | 
| 158 | 
            -
              EXCEPTION_NEVER = {Exception => :never}.freeze
         | 
| 159 | 
            -
              EXCEPTION_IMMEDIATE = {Exception => :immediate}.freeze
         | 
| 160 | 
            +
              EXCEPTION_NEVER = { Exception => :never }.freeze
         | 
| 161 | 
            +
              EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze
         | 
| 160 162 | 
             
              private_constant :EXCEPTION_NEVER
         | 
| 161 163 | 
             
              private_constant :EXCEPTION_IMMEDIATE
         | 
| 162 164 |  | 
| 163 | 
            -
              def try_synchronize
         | 
| 165 | 
            +
              def try_synchronize(&block)
         | 
| 164 166 | 
             
                Thread.handle_interrupt(EXCEPTION_NEVER) do
         | 
| 165 | 
            -
                   | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
                  end
         | 
| 167 | 
            +
                  return false unless @lock.try_lock
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
         | 
| 170 | 
            +
                  true
         | 
| 171 | 
            +
                ensure
         | 
| 172 | 
            +
                  @lock.unlock if @lock.owned?
         | 
| 172 173 | 
             
                end
         | 
| 173 174 | 
             
              end
         | 
| 174 175 | 
             
            end
         | 
    
        data/lib/semian/mysql2.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "semian/adapter"
         | 
| 4 | 
            +
            require "mysql2"
         | 
| 3 5 |  | 
| 4 6 | 
             
            module Mysql2
         | 
| 5 7 | 
             
              Mysql2::Error.include(::Semian::AdapterError)
         | 
| @@ -32,13 +34,13 @@ module Semian | |
| 32 34 | 
             
                CircuitOpenError = ::Mysql2::CircuitOpenError
         | 
| 33 35 | 
             
                PingFailure = Class.new(::Mysql2::Error)
         | 
| 34 36 |  | 
| 35 | 
            -
                DEFAULT_HOST =  | 
| 37 | 
            +
                DEFAULT_HOST = "localhost"
         | 
| 36 38 | 
             
                DEFAULT_PORT = 3306
         | 
| 37 39 |  | 
| 38 40 | 
             
                QUERY_WHITELIST = Regexp.union(
         | 
| 39 | 
            -
                   | 
| 40 | 
            -
                   | 
| 41 | 
            -
                   | 
| 41 | 
            +
                  %r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i,
         | 
| 42 | 
            +
                  %r{\A(?:/\*.*?\*/)?\s*COMMIT}i,
         | 
| 43 | 
            +
                  %r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i,
         | 
| 42 44 | 
             
                )
         | 
| 43 45 |  | 
| 44 46 | 
             
                # The naked methods are exposed as `raw_query` and `raw_connect` for instrumentation purpose
         | 
| @@ -55,7 +57,8 @@ module Semian | |
| 55 57 |  | 
| 56 58 | 
             
                def semian_identifier
         | 
| 57 59 | 
             
                  @semian_identifier ||= begin
         | 
| 58 | 
            -
                     | 
| 60 | 
            +
                    name = semian_options && semian_options[:name]
         | 
| 61 | 
            +
                    unless name
         | 
| 59 62 | 
             
                      host = query_options[:host] || DEFAULT_HOST
         | 
| 60 63 | 
             
                      port = query_options[:port] || DEFAULT_PORT
         | 
| 61 64 | 
             
                      name = "#{host}:#{port}"
         | 
| @@ -68,7 +71,7 @@ module Semian | |
| 68 71 | 
             
                  result = nil
         | 
| 69 72 | 
             
                  acquire_semian_resource(adapter: :mysql, scope: :ping) do
         | 
| 70 73 | 
             
                    result = raw_ping
         | 
| 71 | 
            -
                    raise PingFailure | 
| 74 | 
            +
                    raise PingFailure, result.to_s unless result
         | 
| 72 75 | 
             
                  end
         | 
| 73 76 | 
             
                  result
         | 
| 74 77 | 
             
                rescue ResourceBusyError, CircuitOpenError, PingFailure
         | 
| @@ -113,6 +116,7 @@ module Semian | |
| 113 116 | 
             
                  # data that is not recognized as a valid encoding, in which case we just
         | 
| 114 117 | 
             
                  # return false.
         | 
| 115 118 | 
             
                  return false unless sql.valid_encoding?
         | 
| 119 | 
            +
             | 
| 116 120 | 
             
                  raise
         | 
| 117 121 | 
             
                end
         | 
| 118 122 |  | 
| @@ -132,7 +136,7 @@ module Semian | |
| 132 136 |  | 
| 133 137 | 
             
                def raw_semian_options
         | 
| 134 138 | 
             
                  return query_options[:semian] if query_options.key?(:semian)
         | 
| 135 | 
            -
                  return query_options[ | 
| 139 | 
            +
                  return query_options["semian"] if query_options.key?("semian")
         | 
| 136 140 | 
             
                end
         | 
| 137 141 | 
             
              end
         | 
| 138 142 | 
             
            end
         | 
    
        data/lib/semian/net_http.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "semian/adapter"
         | 
| 4 | 
            +
            require "net/http"
         | 
| 3 5 |  | 
| 4 6 | 
             
            module Net
         | 
| 5 7 | 
             
              ProtocolError.include(::Semian::AdapterError)
         | 
| @@ -40,10 +42,11 @@ module Semian | |
| 40 42 | 
             
                  ::Net::ProtocolError,
         | 
| 41 43 | 
             
                  ::EOFError,
         | 
| 42 44 | 
             
                  ::IOError,
         | 
| 43 | 
            -
                  ::SystemCallError, # includes ::Errno::EINVAL, ::Errno::ECONNRESET, | 
| 45 | 
            +
                  ::SystemCallError, # includes ::Errno::EINVAL, ::Errno::ECONNRESET,
         | 
| 46 | 
            +
                  # ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT, and more
         | 
| 44 47 | 
             
                ].freeze # Net::HTTP can throw many different errors, this tries to capture most of them
         | 
| 45 48 |  | 
| 46 | 
            -
                module ClassMethods | 
| 49 | 
            +
                module ClassMethods
         | 
| 47 50 | 
             
                  def new(*args, semian: true)
         | 
| 48 51 | 
             
                    http = super(*args)
         | 
| 49 52 | 
             
                    http.instance_variable_set(:@semian_enabled, semian)
         | 
| @@ -57,6 +60,7 @@ module Semian | |
| 57 60 |  | 
| 58 61 | 
             
                  def semian_configuration=(configuration)
         | 
| 59 62 | 
             
                    raise Semian::NetHTTP::SemianConfigurationChangedError unless @semian_configuration.nil?
         | 
| 63 | 
            +
             | 
| 60 64 | 
             
                    @semian_configuration = configuration
         | 
| 61 65 | 
             
                  end
         | 
| 62 66 |  | 
| @@ -88,11 +92,13 @@ module Semian | |
| 88 92 |  | 
| 89 93 | 
             
                def connect
         | 
| 90 94 | 
             
                  return super if disabled?
         | 
| 95 | 
            +
             | 
| 91 96 | 
             
                  acquire_semian_resource(adapter: :http, scope: :connection) { super }
         | 
| 92 97 | 
             
                end
         | 
| 93 98 |  | 
| 94 99 | 
             
                def transport_request(*)
         | 
| 95 100 | 
             
                  return super if disabled?
         | 
| 101 | 
            +
             | 
| 96 102 | 
             
                  acquire_semian_resource(adapter: :http, scope: :query) do
         | 
| 97 103 | 
             
                    handle_error_responses(super)
         | 
| 98 104 | 
             
                  end
         | 
    
        data/lib/semian/platform.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Semian
         | 
| 2 4 | 
             
              extend self
         | 
| 3 5 |  | 
| @@ -11,6 +13,6 @@ module Semian | |
| 11 13 | 
             
              end
         | 
| 12 14 |  | 
| 13 15 | 
             
              def disabled?
         | 
| 14 | 
            -
                ENV[ | 
| 16 | 
            +
                ENV["SEMIAN_SEMAPHORES_DISABLED"] || ENV["SEMIAN_DISABLED"]
         | 
| 15 17 | 
             
              end
         | 
| 16 18 | 
             
            end
         | 
| @@ -1,10 +1,12 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Semian
         | 
| 2 4 | 
             
              class ProtectedResource
         | 
| 3 5 | 
             
                extend Forwardable
         | 
| 4 6 |  | 
| 5 7 | 
             
                def_delegators :@bulkhead, :destroy, :count, :semid, :tickets, :registered_workers
         | 
| 6 8 | 
             
                def_delegators :@circuit_breaker, :reset, :mark_failed, :mark_success, :request_allowed?,
         | 
| 7 | 
            -
             | 
| 9 | 
            +
                  :open?, :closed?, :half_open?
         | 
| 8 10 |  | 
| 9 11 | 
             
                attr_reader :bulkhead, :circuit_breaker, :name
         | 
| 10 12 | 
             
                attr_accessor :updated_at
         | 
| @@ -17,8 +19,8 @@ module Semian | |
| 17 19 | 
             
                end
         | 
| 18 20 |  | 
| 19 21 | 
             
                def destroy
         | 
| 20 | 
            -
                  @bulkhead | 
| 21 | 
            -
                  @circuit_breaker | 
| 22 | 
            +
                  @bulkhead&.destroy
         | 
| 23 | 
            +
                  @circuit_breaker&.destroy
         | 
| 22 24 | 
             
                end
         | 
| 23 25 |  | 
| 24 26 | 
             
                def acquire(timeout: nil, scope: nil, adapter: nil, resource: nil)
         | 
    
        data/lib/semian/rails.rb
    CHANGED
    
    | @@ -1,9 +1,15 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
                 | 
| 3 | 
            +
            require "active_record/connection_adapters/abstract_adapter"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ActiveRecord
         | 
| 6 | 
            +
              module ConnectionAdapters
         | 
| 7 | 
            +
                class AbstractAdapter
         | 
| 8 | 
            +
                  def semian_resource
         | 
| 9 | 
            +
                    # support for https://github.com/rails/rails/commit/d86fd6415c0dfce6fadb77e74696cf728e5eb76b
         | 
| 10 | 
            +
                    connection = instance_variable_defined?(:@raw_connection) ? @raw_connection : @connection
         | 
| 11 | 
            +
                    connection.semian_resource
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 8 14 | 
             
              end
         | 
| 9 15 | 
             
            end
         | 
    
        data/lib/semian/redis.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "semian/adapter"
         | 
| 4 | 
            +
            require "redis"
         | 
| 3 5 |  | 
| 4 6 | 
             
            class Redis
         | 
| 5 7 | 
             
              Redis::BaseConnectionError.include(::Semian::AdapterError)
         | 
| @@ -17,7 +19,7 @@ class Redis | |
| 17 19 | 
             
              end
         | 
| 18 20 |  | 
| 19 21 | 
             
              class ConnectionError < Redis::BaseConnectionError
         | 
| 20 | 
            -
                # A Connection Reset is a fast failure and we don't want to track these errors in | 
| 22 | 
            +
                # A Connection Reset is a fast failure and we don't want to track these errors in
         | 
| 21 23 | 
             
                # semian
         | 
| 22 24 | 
             
                def marks_semian_circuits?
         | 
| 23 25 | 
             
                  message != "Connection lost (ECONNRESET)"
         | 
| @@ -90,12 +92,11 @@ module Semian | |
| 90 92 |  | 
| 91 93 | 
             
                def connect
         | 
| 92 94 | 
             
                  acquire_semian_resource(adapter: :redis, scope: :connection) do
         | 
| 93 | 
            -
                     | 
| 94 | 
            -
             | 
| 95 | 
            -
                     | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
                    end
         | 
| 95 | 
            +
                    raw_connect
         | 
| 96 | 
            +
                  rescue SocketError, RuntimeError => e
         | 
| 97 | 
            +
                    raise ResolveError, semian_identifier if dns_resolve_failure?(e.cause || e)
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    raise
         | 
| 99 100 | 
             
                  end
         | 
| 100 101 | 
             
                end
         | 
| 101 102 |  | 
| @@ -107,7 +108,7 @@ module Semian | |
| 107 108 |  | 
| 108 109 | 
             
                  begin
         | 
| 109 110 | 
             
                    connection.timeout = temp_timeout if connected?
         | 
| 110 | 
            -
                    options[:timeout] = Float(temp_timeout) | 
| 111 | 
            +
                    options[:timeout] = Float(temp_timeout)
         | 
| 111 112 | 
             
                    options[:connect_timeout] = Float(temp_timeout)
         | 
| 112 113 | 
             
                    options[:read_timeout] = Float(temp_timeout)
         | 
| 113 114 | 
             
                    options[:write_timeout] = Float(temp_timeout)
         | 
| @@ -133,17 +134,18 @@ module Semian | |
| 133 134 |  | 
| 134 135 | 
             
                def raw_semian_options
         | 
| 135 136 | 
             
                  return options[:semian] if options.key?(:semian)
         | 
| 136 | 
            -
                  return options[ | 
| 137 | 
            +
                  return options["semian"] if options.key?("semian")
         | 
| 137 138 | 
             
                end
         | 
| 138 139 |  | 
| 139 140 | 
             
                def raise_if_out_of_memory(reply)
         | 
| 140 141 | 
             
                  return unless reply.is_a?(::Redis::CommandError)
         | 
| 141 142 | 
             
                  return unless reply.message.start_with?("OOM ")
         | 
| 142 | 
            -
             | 
| 143 | 
            +
             | 
| 144 | 
            +
                  raise ::Redis::OutOfMemoryError, reply.message
         | 
| 143 145 | 
             
                end
         | 
| 144 146 |  | 
| 145 147 | 
             
                def dns_resolve_failure?(e)
         | 
| 146 | 
            -
                  e.to_s.match?(/(can't resolve)|(name or service not known)|(nodename nor servname provided, or not known)|(failure in name resolution)/i)
         | 
| 148 | 
            +
                  e.to_s.match?(/(can't resolve)|(name or service not known)|(nodename nor servname provided, or not known)|(failure in name resolution)/i) # rubocop:disable Layout/LineLength
         | 
| 147 149 | 
             
                end
         | 
| 148 150 | 
             
              end
         | 
| 149 151 | 
             
            end
         | 
    
        data/lib/semian/redis_client.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "semian/adapter"
         | 
| 4 | 
            +
            require "redis-client"
         | 
| 3 5 |  | 
| 4 6 | 
             
            class RedisClient
         | 
| 5 7 | 
             
              ConnectionError.include(::Semian::AdapterError)
         | 
| @@ -104,7 +106,7 @@ module Semian | |
| 104 106 | 
             
                include RedisClientCommon
         | 
| 105 107 | 
             
                define_method(:semian_resource, Semian::Adapter.instance_method(:semian_resource))
         | 
| 106 108 | 
             
                define_method(:clear_semian_resource, Semian::Adapter.instance_method(:clear_semian_resource))
         | 
| 107 | 
            -
             | 
| 109 | 
            +
              end
         | 
| 108 110 | 
             
            end
         | 
| 109 111 |  | 
| 110 112 | 
             
            RedisClient.prepend(Semian::RedisClient)
         | 
    
        data/lib/semian/resource.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Semian
         | 
| 2 | 
            -
              class Resource  | 
| 3 | 
            -
                attr_reader : | 
| 4 | 
            +
              class Resource # :nodoc:
         | 
| 5 | 
            +
                attr_reader :name
         | 
| 4 6 |  | 
| 5 7 | 
             
                class << Semian::Resource
         | 
| 6 8 | 
             
                  # Ensure that there can only be one resource of a given type
         | 
| @@ -55,7 +57,7 @@ module Semian | |
| 55 57 | 
             
                end
         | 
| 56 58 |  | 
| 57 59 | 
             
                def key
         | 
| 58 | 
            -
                   | 
| 60 | 
            +
                  "0x00000000"
         | 
| 59 61 | 
             
                end
         | 
| 60 62 |  | 
| 61 63 | 
             
                def in_use?
         | 
| @@ -1,11 +1,13 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "thread"
         | 
| 2 4 |  | 
| 3 5 | 
             
            module Semian
         | 
| 4 6 | 
             
              module Simple
         | 
| 5 | 
            -
                class SlidingWindow  | 
| 7 | 
            +
                class SlidingWindow # :nodoc:
         | 
| 6 8 | 
             
                  extend Forwardable
         | 
| 7 9 |  | 
| 8 | 
            -
                  def_delegators :@window, :size, :last
         | 
| 10 | 
            +
                  def_delegators :@window, :size, :last, :empty?
         | 
| 9 11 | 
             
                  attr_reader :max_size
         | 
| 10 12 |  | 
| 11 13 | 
             
                  # A sliding window is a structure that stores the most @max_size recent timestamps
         | 
    
        data/lib/semian/simple_state.rb
    CHANGED
    
    
    
        data/lib/semian/version.rb
    CHANGED