brow 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94b1026b806df0a58a01aa0bc911ca756251dc5d0de510ae1c12d42665b00559
4
- data.tar.gz: 55b2b5454086ac78e22681ced81bb6d500dd9c6537ebfcf67bbc9a445a2d9a79
3
+ metadata.gz: 6b51fe73e4a857aa33f3546cf5d7f0e434f467c19e585e00e8b7a5535765d0b4
4
+ data.tar.gz: b36b39d1293d6b32940b5f9306c7a679c6438cc76377ea376560223343c3e6e3
5
5
  SHA512:
6
- metadata.gz: 2d23c1ff291ca611a65b1357392e2f4275502fc3467a629e85ff631f7585050095ef4cc86f0b86981fb0e7e790ce709bd2a0e2b511f0e706954cec6221ae40db
7
- data.tar.gz: 884ffd8cebbcaf2681a1347a1115892676c86de12dcef693e5364531e37cef0d3700343cb8e4d2393270003c541d9efbd0e1c427fef904b66f475eb81ece2246
6
+ metadata.gz: 7bea156ce56ed4bc49688db5dfd9ce810fcbc6e6a18513445dc734792374b0831063e7ac831c9a1eed4f50459d95cef38181afa3071e5336e1f671a576844938
7
+ data.tar.gz: ad5cc322c41f678f7de7df6add329defc9c90789fbe2a71df62156d58500a3b65648dacf0bc5bdeb0d986a5d2f542cccceb6e85dd55ef9b679c23a18f81ffc90
data/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2021-11-04
9
+
10
+ ### Added
11
+
12
+ - Allow configuring most options from ENV variables by default (fb7819b0237a81e573677f3050446a4f41e8fb47).
13
+ - Extra early return to avoid mutex lock if thread is alive (ac7dcfe54ee83b18e0df5ab3778a077584c843bd).
14
+ - Validation on many of the configuration options (c50b11a2917272a87937f8aa86007816a87c63a2 and 07e2581397f870249a347d4d68e4fce172d33cef).
15
+
16
+ ### Changed
17
+
18
+ - Stop stringifying keys. Just enqueue whatever is passed and let JSON do the rest (2e63d5328e048f0fad9fc41ca0935f97fb5ada2f).
19
+ - A bunch of test stuff to make them faster and less flaky.
20
+
8
21
  ## [0.3.0] - 2021-10-29
9
22
 
10
23
  https://github.com/jnunemaker/brow/pull/4
data/Gemfile CHANGED
@@ -8,7 +8,7 @@ gem "minitest", "~> 5.0"
8
8
  gem "maxitest", "~> 4.1"
9
9
  gem "minitest-heat", "~> 0.0"
10
10
  gem "webmock", "~> 3.10.0"
11
- gem "rack", "~> 2.2.3"
11
+ gem "climate_control", "~> 0.2.0"
12
12
 
13
13
  group(:guard) do
14
14
  gem "guard", "~> 2.18.0"
data/Guardfile CHANGED
@@ -32,6 +32,7 @@ guard :minitest do
32
32
  watch(%r{^test/(.*)\/?(.*)_test\.rb$})
33
33
  watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
34
34
  watch(%r{^test/test_helper\.rb$}) { 'test' }
35
+ watch(%r{^test/support/fake_server\.rb$}) { 'test' }
35
36
 
36
37
  # with Minitest::Spec
37
38
  # watch(%r{^spec/(.*)_spec\.rb$})
data/examples/basic.rb CHANGED
@@ -1,20 +1,14 @@
1
1
  require_relative "../lib/brow"
2
+ require_relative "echo_server"
2
3
 
3
4
  client = Brow::Client.new({
4
- url: "https://requestbin.net/r/2bp3p3vn",
5
+ url: "http://localhost:#{EchoServer.instance.port}",
5
6
  batch_size: 10,
6
7
  })
7
8
 
8
9
  5.times do |n|
9
- client.push(n: n, parent: true)
10
+ client.push({
11
+ n: n,
12
+ now: Time.now.utc,
13
+ })
10
14
  end
11
-
12
- pid = fork {
13
- 15.times do |n|
14
- client.push({
15
- number: n,
16
- now: Time.now.utc,
17
- })
18
- end
19
- }
20
- Process.waitpid pid, 0
@@ -0,0 +1,51 @@
1
+ # Usage: bundle exec ruby examples/echo_server.rb
2
+ #
3
+ # By default this starts in thread that other example scripts can use.
4
+ #
5
+ # By setting FOREGROUND=1, this will run in the foreground instead of
6
+ # background thread.
7
+ #
8
+ # FOREGROUND=1 bundle exec ruby examples/echo_server.rb
9
+ require "socket"
10
+ require "thread"
11
+ require "logger"
12
+ require "json"
13
+ require "rack"
14
+ require "rack/handler/webrick"
15
+
16
+ class EchoServer
17
+ include Singleton
18
+
19
+ attr_reader :port, :thread
20
+
21
+ def initialize
22
+ @logger = Logger.new(STDOUT)
23
+ @logger.level = Logger::INFO
24
+ @port = ENV.fetch("PORT", 9999)
25
+ @started = false
26
+
27
+ @server = WEBrick::HTTPServer.new({
28
+ Port: @port,
29
+ StartCallback: -> { @started = true },
30
+ Logger: WEBrick::Log.new(@logger, WEBrick::Log::INFO),
31
+ AccessLog: [
32
+ [@logger, WEBrick::AccessLog::COMMON_LOG_FORMAT],
33
+ ],
34
+ })
35
+
36
+ @server.mount '/', Rack::Handler::WEBrick, ->(env) {
37
+ request = Rack::Request.new(env)
38
+ @logger.debug JSON.parse(request.body.read).inspect
39
+ [200, {}, [""]]
40
+ }
41
+
42
+ @thread = Thread.new { @server.start }
43
+ Timeout.timeout(10) { :wait until @started }
44
+ end
45
+ end
46
+
47
+ EchoServer.instance
48
+
49
+ if ENV.fetch("FOREGROUND", "0") == "1"
50
+ EchoServer.instance.thread.join
51
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "../lib/brow"
2
+ require_relative "echo_server"
3
+
4
+ client = Brow::Client.new({
5
+ url: "http://localhost:#{EchoServer.instance.port}",
6
+ batch_size: 10,
7
+ })
8
+
9
+ client.push({
10
+ now: Time.now.utc,
11
+ parent: true,
12
+ })
13
+
14
+ pid = fork {
15
+ client.push({
16
+ now: Time.now.utc,
17
+ child: true,
18
+ })
19
+ }
20
+ Process.waitpid pid, 0
@@ -0,0 +1,32 @@
1
+ require_relative "../lib/brow"
2
+
3
+ port = ENV.fetch("PORT") { 9999 }
4
+
5
+ if ENV.fetch("START_SERVER", "1") == "1"
6
+ require_relative "echo_server"
7
+ port = EchoServer.instance.port
8
+ end
9
+
10
+ Brow.logger = Logger.new(STDOUT)
11
+ Brow.logger.level = Logger::INFO
12
+
13
+ client = Brow::Client.new({
14
+ url: "http://localhost:#{port}",
15
+ batch_size: 1_000,
16
+ })
17
+
18
+ running = true
19
+
20
+ trap("INT") {
21
+ puts "Shutting down"
22
+ running = false
23
+ }
24
+
25
+ while running
26
+ rand(10_000).times { client.push("foo" => "bar") }
27
+
28
+ puts "Queue size: #{client.worker.queue.size}"
29
+
30
+ # Pretend to work
31
+ sleep(rand)
32
+ end
@@ -32,10 +32,30 @@ module Brow
32
32
  # :randomization_factor - The randomization factor to use to create a range
33
33
  # around the retry interval.
34
34
  def initialize(options = {})
35
- @min_timeout_ms = options[:min_timeout_ms] || MIN_TIMEOUT_MS
36
- @max_timeout_ms = options[:max_timeout_ms] || MAX_TIMEOUT_MS
37
- @multiplier = options[:multiplier] || MULTIPLIER
38
- @randomization_factor = options[:randomization_factor] || RANDOMIZATION_FACTOR
35
+ @min_timeout_ms = options.fetch(:min_timeout_ms) {
36
+ ENV.fetch("BROW_BACKOFF_MIN_TIMEOUT_MS", MIN_TIMEOUT_MS).to_i
37
+ }
38
+ @max_timeout_ms = options.fetch(:max_timeout_ms) {
39
+ ENV.fetch("BROW_BACKOFF_MAX_TIMEOUT_MS", MAX_TIMEOUT_MS).to_i
40
+ }
41
+ @multiplier = options.fetch(:multiplier) {
42
+ ENV.fetch("BROW_BACKOFF_MULTIPLIER", MULTIPLIER).to_f
43
+ }
44
+ @randomization_factor = options.fetch(:randomization_factor) {
45
+ ENV.fetch("BROW_BACKOFF_RANDOMIZATION_FACTOR", RANDOMIZATION_FACTOR).to_f
46
+ }
47
+
48
+ unless @min_timeout_ms >= 0
49
+ raise ArgumentError, ":min_timeout_ms must be >= 0 but was #{@min_timeout_ms.inspect}"
50
+ end
51
+
52
+ unless @max_timeout_ms >= 0
53
+ raise ArgumentError, ":max_timeout_ms must be >= 0 but was #{@max_timeout_ms.inspect}"
54
+ end
55
+
56
+ unless @min_timeout_ms <= max_timeout_ms
57
+ raise ArgumentError, ":min_timeout_ms (#{@min_timeout_ms.inspect}) must be <= :max_timeout_ms (#{@max_timeout_ms.inspect})"
58
+ end
39
59
 
40
60
  @attempts = 0
41
61
  end
@@ -25,11 +25,18 @@ module Brow
25
25
  def_delegators :@messages, :size
26
26
  def_delegators :@messages, :count
27
27
 
28
- attr_reader :uuid, :json_size
28
+ attr_reader :uuid, :json_size, :max_size
29
29
 
30
30
  def initialize(options = {})
31
31
  clear
32
- @max_size = options[:max_size] || MAX_SIZE
32
+ @max_size = options.fetch(:max_size) {
33
+ ENV.fetch("BROW_BATCH_SIZE", MAX_SIZE).to_i
34
+ }
35
+
36
+ unless @max_size > 0
37
+ raise ArgumentError, ":max_size must be > 0 but was #{@max_size.inspect}"
38
+ end
39
+
33
40
  @logger = options.fetch(:logger) { Brow.logger }
34
41
  end
35
42
 
@@ -3,6 +3,7 @@
3
3
  require 'net/http'
4
4
  require 'net/https'
5
5
  require 'json'
6
+ require 'set'
6
7
 
7
8
  require_relative 'response'
8
9
  require_relative 'backoff_policy'
@@ -18,20 +19,40 @@ module Brow
18
19
  # Private: Default open timeout on requests.
19
20
  OPEN_TIMEOUT = 4
20
21
 
22
+ # Private: Default write timeout on requests.
23
+ WRITE_TIMEOUT = 4
24
+
25
+ # Private: URL schemes that this transport supports.
26
+ VALID_HTTP_SCHEMES = Set["http", "https"].freeze
27
+
21
28
  # Private
22
29
  attr_reader :url, :headers, :retries, :logger, :backoff_policy, :http
23
30
 
24
31
  def initialize(options = {})
25
- @url = options[:url] || raise(ArgumentError, ":url is required to be present so we know where to send batches")
32
+ @url = options.fetch(:url) {
33
+ ENV.fetch("BROW_URL") {
34
+ raise ArgumentError, ":url is required to be present so we know where to send batches"
35
+ }
36
+ }
26
37
  @uri = URI.parse(@url)
27
38
 
39
+ unless VALID_HTTP_SCHEMES.include?(@uri.scheme)
40
+ raise ArgumentError, ":url was must be http(s) scheme but was #{@uri.scheme.inspect}"
41
+ end
42
+
28
43
  # Default path if people forget a slash.
29
44
  if @uri.path.nil? || @uri.path.empty?
30
45
  @uri.path = "/"
31
46
  end
32
47
 
33
48
  @headers = options[:headers] || {}
34
- @retries = options[:retries] || RETRIES
49
+ @retries = options.fetch(:retries) {
50
+ ENV.fetch("BROW_RETRIES", RETRIES).to_i
51
+ }
52
+
53
+ unless @retries >= 0
54
+ raise ArgumentError, ":retries must be >= to 0 but was #{@retries.inspect}"
55
+ end
35
56
 
36
57
  @logger = options.fetch(:logger) { Brow.logger }
37
58
  @backoff_policy = options.fetch(:backoff_policy) {
@@ -40,8 +61,25 @@ module Brow
40
61
 
41
62
  @http = Net::HTTP.new(@uri.host, @uri.port)
42
63
  @http.use_ssl = @uri.scheme == "https"
43
- @http.read_timeout = options[:read_timeout] || READ_TIMEOUT
44
- @http.open_timeout = options[:open_timeout] || OPEN_TIMEOUT
64
+
65
+ read_timeout = options.fetch(:read_timeout) {
66
+ ENV.fetch("BROW_READ_TIMEOUT", READ_TIMEOUT).to_f
67
+ }
68
+ @http.read_timeout = read_timeout if read_timeout
69
+
70
+ open_timeout = options.fetch(:open_timeout) {
71
+ ENV.fetch("BROW_OPEN_TIMEOUT", OPEN_TIMEOUT).to_f
72
+ }
73
+ @http.open_timeout = open_timeout if open_timeout
74
+
75
+ if RUBY_VERSION >= '2.6.0'
76
+ write_timeout = options.fetch(:write_timeout) {
77
+ ENV.fetch("BROW_WRITE_TIMEOUT", WRITE_TIMEOUT).to_f
78
+ }
79
+ @http.write_timeout = write_timeout if write_timeout
80
+ else
81
+ Kernel.warn("Warning: option :write_timeout requires Ruby version 2.6.0 or later")
82
+ end
45
83
  end
46
84
 
47
85
  # Sends a batch of messages to the API
@@ -128,7 +166,7 @@ module Brow
128
166
  headers = {
129
167
  "Accept" => "application/json",
130
168
  "Content-Type" => "application/json",
131
- "User-Agent" => "brow-ruby/#{Brow::VERSION}",
169
+ "User-Agent" => "Brow v#{Brow::VERSION}",
132
170
  "Client-Language" => "ruby",
133
171
  "Client-Language-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
134
172
  "Client-Platform" => RUBY_PLATFORM,
data/lib/brow/utils.rb CHANGED
@@ -13,13 +13,6 @@ module Brow
13
13
  end
14
14
  end
15
15
 
16
- # Internal: Return a new hash with keys converted to strings
17
- def stringify_keys(hash)
18
- hash.each_with_object({}) do |(k, v), memo|
19
- memo[k.to_s] = v
20
- end
21
- end
22
-
23
16
  # Internal: Returns a new hash with all the date values in the into
24
17
  # iso8601 strings
25
18
  def isoify_dates(hash)
data/lib/brow/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brow
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/brow/worker.rb CHANGED
@@ -32,8 +32,8 @@ module Brow
32
32
  # The worker continuously takes messages off the queue and makes requests to
33
33
  # the api.
34
34
  #
35
- # queue - Queue synchronized between client and worker
36
35
  # options - The Hash of worker options.
36
+ # :queue - Queue synchronized between client and worker
37
37
  # :on_error - Proc of what to do on an error.
38
38
  # :batch_size - Fixnum of how many items to send in a batch.
39
39
  # :transport - The Transport object to deliver batches.
@@ -42,6 +42,8 @@ module Brow
42
42
  # via Transport.
43
43
  # :shutdown_timeout - The number of seconds to wait for the worker thread
44
44
  # to join when shutting down.
45
+ # :start_automatically - Should the client start the worker thread
46
+ # automatically and keep it running.
45
47
  # :shutdown_automatically - Should the client shutdown automatically or
46
48
  # manually. If true, shutdown is automatic. If
47
49
  # false, you'll need to handle this on your own.
@@ -52,11 +54,32 @@ module Brow
52
54
  @mutex = Mutex.new
53
55
  options = Brow::Utils.symbolize_keys(options)
54
56
  @on_error = options[:on_error] || DEFAULT_ON_ERROR
55
- @batch_size = options[:batch_size]
56
- @max_queue_size = options.fetch(:max_queue_size) { MAX_QUEUE_SIZE }
57
57
  @logger = options.fetch(:logger) { Brow.logger }
58
58
  @transport = options.fetch(:transport) { Transport.new(options) }
59
- @shutdown_timeout = options.fetch(:shutdown_timeout) { SHUTDOWN_TIMEOUT }
59
+
60
+ @batch_size = options.fetch(:batch_size) {
61
+ ENV.fetch("BROW_BATCH_SIZE", MessageBatch::MAX_SIZE).to_i
62
+ }
63
+ @max_queue_size = options.fetch(:max_queue_size) {
64
+ ENV.fetch("BROW_MAX_QUEUE_SIZE", MAX_QUEUE_SIZE).to_i
65
+ }
66
+ @shutdown_timeout = options.fetch(:shutdown_timeout) {
67
+ ENV.fetch("BROW_SHUTDOWN_TIMEOUT", SHUTDOWN_TIMEOUT).to_f
68
+ }
69
+
70
+ if @batch_size <= 0
71
+ raise ArgumentError, ":batch_size must be greater than 0"
72
+ end
73
+
74
+ if @max_queue_size <= 0
75
+ raise ArgumentError, ":max_queue_size must be greater than 0"
76
+ end
77
+
78
+ if @shutdown_timeout <= 0
79
+ raise ArgumentError, ":shutdown_timeout must be greater than 0"
80
+ end
81
+
82
+ @start_automatically = options.fetch(:start_automatically, true)
60
83
 
61
84
  if options.fetch(:shutdown_automatically, true)
62
85
  at_exit { stop }
@@ -65,9 +88,9 @@ module Brow
65
88
 
66
89
  def push(data)
67
90
  raise ArgumentError, "data must be a Hash" unless data.is_a?(Hash)
68
- start
91
+ start if @start_automatically
69
92
 
70
- data = Utils.isoify_dates(Utils.stringify_keys(data))
93
+ data = Utils.isoify_dates(data)
71
94
 
72
95
  if queue.length < max_queue_size
73
96
  queue << data
@@ -108,6 +131,7 @@ module Brow
108
131
 
109
132
  case message
110
133
  when SHUTDOWN
134
+ logger.info("[brow]") { "Worker shutting down" }
111
135
  send_batch(batch) unless batch.empty?
112
136
  break
113
137
  else
@@ -131,12 +155,15 @@ module Brow
131
155
  end
132
156
 
133
157
  def ensure_worker_running
158
+ # Return early if thread is alive and avoid the mutex lock and unlock.
159
+ return if thread_alive?
160
+
134
161
  # If another thread is starting worker thread, then return early so this
135
162
  # thread can enqueue and move on with life.
136
163
  return unless mutex.try_lock
137
164
 
138
165
  begin
139
- return if @thread && @thread.alive?
166
+ return if thread_alive?
140
167
  @thread = Thread.new { run }
141
168
  logger.debug("[brow]") { "Worker thread [#{@thread.object_id}] started" }
142
169
  ensure
@@ -144,6 +171,10 @@ module Brow
144
171
  end
145
172
  end
146
173
 
174
+ def thread_alive?
175
+ @thread && @thread.alive?
176
+ end
177
+
147
178
  def reset
148
179
  @pid = Process.pid
149
180
  mutex.unlock if mutex.locked?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-29 00:00:00.000000000 Z
11
+ date: 2021-11-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -26,6 +26,9 @@ files:
26
26
  - bin/console
27
27
  - bin/setup
28
28
  - examples/basic.rb
29
+ - examples/echo_server.rb
30
+ - examples/forked.rb
31
+ - examples/long_running.rb
29
32
  - lib/brow.rb
30
33
  - lib/brow/backoff_policy.rb
31
34
  - lib/brow/client.rb