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 +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
|