sqewer 5.0.9 → 5.1.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 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