brow 0.3.0 → 0.4.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 +13 -0
- data/Gemfile +1 -1
- data/Guardfile +1 -0
- data/examples/basic.rb +6 -12
- data/examples/echo_server.rb +51 -0
- data/examples/forked.rb +20 -0
- data/examples/long_running.rb +32 -0
- data/lib/brow/backoff_policy.rb +24 -4
- data/lib/brow/message_batch.rb +9 -2
- data/lib/brow/transport.rb +43 -5
- data/lib/brow/utils.rb +0 -7
- data/lib/brow/version.rb +1 -1
- data/lib/brow/worker.rb +38 -7
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b51fe73e4a857aa33f3546cf5d7f0e434f467c19e585e00e8b7a5535765d0b4
|
4
|
+
data.tar.gz: b36b39d1293d6b32940b5f9306c7a679c6438cc76377ea376560223343c3e6e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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: "
|
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(
|
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
|
data/examples/forked.rb
ADDED
@@ -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
|
data/lib/brow/backoff_policy.rb
CHANGED
@@ -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
|
36
|
-
|
37
|
-
|
38
|
-
@
|
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
|
data/lib/brow/message_batch.rb
CHANGED
@@ -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
|
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
|
|
data/lib/brow/transport.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
44
|
-
|
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" => "
|
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
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
|
-
|
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(
|
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
|
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.
|
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-
|
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
|