sqewer 5.0.9 → 5.1.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
  SHA1:
3
- metadata.gz: 84caf342300d2128e0e128efe4d235413bf011bc
4
- data.tar.gz: f695a18f5e6b94a5c1854e4a5eb973ab7066def1
3
+ metadata.gz: a3b121d612798813c7a9de3e795f82603cc57b9c
4
+ data.tar.gz: 4fb43eefa14ff0fe3c2838b34d8c7d787ebb8481
5
5
  SHA512:
6
- metadata.gz: 408c64f831f9441ed93609b07f037dff99de6af2d5ece6daebc95bf604c43a6518e2a9dc18a5c8ec37cdfe5d138f0d1a1b3a82b9b1a6554f4b34832992d275b0
7
- data.tar.gz: 9b1c80ccadffe1b7f006a3310272d9fbdc715d3c45e22c1fcf8ace0d9bc8e2d300ac27096a745af0e80d031b41b454f60c46c6830263542c512cb7382312cd4d
6
+ metadata.gz: ce64c01eae4696d64c4215757c18afee1cefd3d99de906a5c8a12f7f28f265533a33a48aa4bbe434190f01fd76892334016bc493c0160146c90ab049f75e26a9
7
+ data.tar.gz: c1c8a1f5882fc38da3026cedd2fa3c1bd35ee1317061b49ff3c69dfd50ebe1ebf5880fad7d14d77ceb441d3b68a10c5c17d6a0c3fdf14ed3f100ccd8ceb92684
@@ -25,11 +25,18 @@ class Sqewer::Connection
25
25
  end
26
26
 
27
27
  # Returns the default adapter, connected to the queue set via the `SQS_QUEUE_URL`
28
- # environment variable.
28
+ # environment variable. Switches to SQLite-backed local queue if the SQS_QUEUE_URL
29
+ # is prefixed with 'sqlite3://'
29
30
  def self.default
30
- new(ENV.fetch('SQS_QUEUE_URL'))
31
+ url_str = ENV.fetch('SQS_QUEUE_URL')
32
+ uri = URI(url_str)
33
+ if uri.scheme == 'sqlite3'
34
+ Sqewer::LocalConnection.new(uri.to_s)
35
+ else
36
+ new(uri.to_s)
37
+ end
31
38
  rescue KeyError => e
32
- raise "SQS_QUEUE_URL not set in the environment. This is the queue URL that the default that Sqewer uses"
39
+ raise "SQS_QUEUE_URL not set in the environment. This is the queue URL Sqewer uses by default."
33
40
  end
34
41
 
35
42
  # Initializes a new adapter, with access to the SQS queue at the given URL.
@@ -0,0 +1,154 @@
1
+ require 'rack'
2
+ class Sqewer::LocalConnection < Sqewer::Connection
3
+ ASSUME_DEADLETTER_AFTER_N_DELIVERIES = 10
4
+
5
+ def self.parse_queue_url(queue_url_starting_with_sqlite3_proto)
6
+ uri = URI.parse(queue_url_starting_with_sqlite3_proto)
7
+
8
+ unless uri.scheme == 'sqlite3'
9
+ raise "The scheme of the SQS queue URL should be with `sqlite3' but was %s" % uri.scheme
10
+ end
11
+
12
+ path_components = ['/', uri.hostname, uri.path].reject(&:nil?).reject(&:empty?).join('/').squeeze('/')
13
+ dbfile_path = File.expand_path(path_components)
14
+
15
+ queue_name = Rack::Utils.parse_nested_query(uri.query).fetch('queue', 'sqewer_local')
16
+
17
+ [dbfile_path, queue_name]
18
+ end
19
+
20
+ def initialize(queue_url_with_sqlite3_scheme)
21
+ require 'sqlite3'
22
+ @db_path, @queue_name = self.class.parse_queue_url(queue_url_with_sqlite3_scheme)
23
+ with_db do |db|
24
+ db.execute("CREATE TABLE IF NOT EXISTS sqewer_messages_v2 (
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT ,
26
+ queue_name VARCHAR NOT NULL,
27
+ receipt_handle VARCHAR NOT NULL,
28
+ deliver_after_epoch INTEGER,
29
+ times_delivered_so_far INTEGER DEFAULT 0,
30
+ last_delivery_at_epoch INTEGER,
31
+ visible BOOLEAN DEFAULT 't',
32
+ message_body TEXT)"
33
+ )
34
+ db.execute("CREATE INDEX IF NOT EXISTS on_receipt_handle ON sqewer_messages_v2 (receipt_handle)")
35
+ db.execute("CREATE INDEX IF NOT EXISTS on_queue_name ON sqewer_messages_v2 (queue_name)")
36
+ end
37
+ rescue LoadError => e
38
+ raise e, "You need the sqlite3 gem in your Gemfile to use LocalConnection. Add it to your Gemfile (`gem 'sqlite3'')"
39
+ end
40
+
41
+ # @return [Array<Message>] an array of Message objects
42
+ def receive_messages
43
+ messages = load_receipt_handles_and_bodies
44
+ messages.map {|message| Message.new(message[0], message[1]) }
45
+ end
46
+
47
+ # @yield [#send_message] the object you can send messages through (will be flushed at method return)
48
+ # @return [void]
49
+ def send_multiple_messages
50
+ buffer = SendBuffer.new
51
+ yield(buffer)
52
+ messages = buffer.messages
53
+ persist_messages(messages)
54
+ end
55
+
56
+ # Deletes multiple messages after they all have been succesfully decoded and processed.
57
+ #
58
+ # @yield [#delete_message] an object you can delete an individual message through
59
+ # @return [void]
60
+ def delete_multiple_messages
61
+ buffer = DeleteBuffer.new
62
+ yield(buffer)
63
+ delete_persisted_messages(buffer.messages)
64
+ end
65
+
66
+ # Only gets used in tests
67
+ def truncate!
68
+ with_db do |db|
69
+ db.execute("DELETE FROM sqewer_messages_v2 WHERE queue_name = ?", @queue_name)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def with_db(**k)
76
+ SQLite3::Database.open(@db_path, **k) do |db|
77
+ db.busy_timeout = 5
78
+ return yield db
79
+ end
80
+ rescue SQLite3::CantOpenException => e
81
+ message_with_path = [e.message, 'at', @db_path].join(' ')
82
+ raise SQLite3::CantOpenException.new(message_with_path)
83
+ end
84
+
85
+ def with_readonly_db(&blk)
86
+ with_db(readonly: true, &blk)
87
+ end
88
+
89
+ def delete_persisted_messages(messages)
90
+ ids_to_delete = messages.map{|m| m.fetch(:receipt_handle) }
91
+ with_db do |db|
92
+ db.execute("BEGIN")
93
+ ids_to_delete.each do |id|
94
+ db.execute("DELETE FROM sqewer_messages_v2 WHERE receipt_handle = ?", id)
95
+ end
96
+ db.execute("COMMIT")
97
+ end
98
+ end
99
+
100
+ def load_receipt_handles_and_bodies
101
+ t = Time.now.to_i
102
+
103
+ # First make messages that were previously marked invisible but not deleted visible again
104
+ with_db do |db|
105
+ db.execute("BEGIN")
106
+ # Make messages visible that have to be redelivered
107
+ db.execute("UPDATE sqewer_messages_v2
108
+ SET visible = 't'
109
+ WHERE queue_name = ? AND visible = 'f' AND last_delivery_at_epoch < ?", @queue_name.to_s, t - 60)
110
+ # Remove hopeless messages
111
+ db.execute("DELETE FROM sqewer_messages_v2
112
+ WHERE queue_name = ? AND times_delivered_so_far >= ?", @queue_name.to_s, ASSUME_DEADLETTER_AFTER_N_DELIVERIES)
113
+ db.execute("COMMIT")
114
+ end
115
+
116
+ rows = with_readonly_db do |db|
117
+ db.execute("SELECT id, receipt_handle, message_body FROM sqewer_messages_v2
118
+ WHERE queue_name = ? AND visible = 't' AND deliver_after_epoch <= ? AND last_delivery_at_epoch <= ?",
119
+ @queue_name.to_s, t, t)
120
+ end
121
+
122
+ with_db do |db|
123
+ db.execute("BEGIN")
124
+ rows.map do |(id, *_)|
125
+ db.execute("UPDATE sqewer_messages_v2
126
+ SET visible = 'f', times_delivered_so_far = times_delivered_so_far + 1, last_delivery_at_epoch = ?
127
+ WHERE id = ?", t, id)
128
+ end
129
+ db.execute("COMMIT")
130
+ end
131
+
132
+ rows.map do |(_, *receipt_handle_and_body)|
133
+ receipt_handle_and_body
134
+ end
135
+ end
136
+
137
+ def persist_messages(messages)
138
+ epoch = Time.now.to_i
139
+ bodies_and_deliver_afters = messages.map do |msg|
140
+ [msg.fetch(:message_body), epoch + msg.fetch(:delay_seconds, 0)]
141
+ end
142
+
143
+ with_db do |db|
144
+ db.execute("BEGIN")
145
+ bodies_and_deliver_afters.map do |body, deliver_after_epoch|
146
+ db.execute("INSERT INTO sqewer_messages_v2
147
+ (queue_name, receipt_handle, message_body, deliver_after_epoch, last_delivery_at_epoch)
148
+ VALUES(?, ?, ?, ?, ?)",
149
+ @queue_name.to_s, SecureRandom.uuid, body, deliver_after_epoch, epoch)
150
+ end
151
+ db.execute("COMMIT")
152
+ end
153
+ end
154
+ end
@@ -1,3 +1,3 @@
1
1
  module Sqewer
2
- VERSION = '5.0.9'
2
+ VERSION = '5.1.0'
3
3
  end
data/lib/sqewer/worker.rb CHANGED
@@ -110,7 +110,7 @@ class Sqewer::Worker
110
110
  @logger.debug { "[worker] Received and buffered %d messages" % messages.length } if messages.any?
111
111
  else
112
112
  @logger.debug { "[worker] No messages received" }
113
- Thread.pass
113
+ sleep SLEEP_SECONDS_ON_EMPTY_QUEUE
114
114
  end
115
115
  else
116
116
  @logger.debug { "[worker] Cache is full (%d items), postponing receive" % @execution_queue.length }
data/sqewer.gemspec CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  spec.add_runtime_dependency 'aws-sdk', '~> 2'
33
+ spec.add_runtime_dependency 'rack'
33
34
  spec.add_runtime_dependency 'very_tiny_state_machine'
34
35
  spec.add_runtime_dependency 'ks'
35
36
  spec.add_runtime_dependency 'retriable'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqewer
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.9
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-23 00:00:00.000000000 Z
11
+ date: 2017-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: very_tiny_state_machine
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -224,6 +238,7 @@ files:
224
238
  - lib/sqewer/extensions/active_job_adapter.rb
225
239
  - lib/sqewer/extensions/appsignal_wrapper.rb
226
240
  - lib/sqewer/extensions/railtie.rb
241
+ - lib/sqewer/local_connection.rb
227
242
  - lib/sqewer/middleware_stack.rb
228
243
  - lib/sqewer/null_logger.rb
229
244
  - lib/sqewer/resubmit.rb