insque 0.6.1 → 0.6.2

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: 41594a38f5f9ff47f8ed7bab78c5d4f488f7b733
4
- data.tar.gz: 95c6383a58ce039840906c5684b336a9f273e659
3
+ metadata.gz: 4fb5eb7a565496e70aee0f071c982f638f9b879a
4
+ data.tar.gz: 74447dc1cd8aecb247fcf1821a5baee070f02132
5
5
  SHA512:
6
- metadata.gz: 2c518a09723ab1b90f04b693c154cceac146a47e664ba24376ee802c5770aa7285602e96019d0edbe2b76469ca16bb1de4d555a09d5435a91a00e419bedf6038
7
- data.tar.gz: f29dd77fbf9d9cd0b0cc5efbf39e07a1aa5fa9864770a4d34fa3f53be35dfb8fc6a8df76d75a10864e68e897ae7a0bbf1a4ab6a636bce45634cc9c76147b0e00
6
+ metadata.gz: 9eb282aa17671257da5d114e78e36c90b772d049e7d6bd4712a27981122b26513fb0698003dc68bde1aa0e8abecafd76419fec268a9af7740f57b7f3c8b4cd05
7
+ data.tar.gz: 51f3e733493a17e601f53ab14003fa60f1accf7514c3f8fe4b864c95a2892487911dc01abdfbebcf92a72d598a8a3b9514903edd73f85fdece6f266eb72d3a1d
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ db/*
data/insque.gemspec CHANGED
@@ -17,4 +17,9 @@ Gem::Specification.new do |gem|
17
17
  gem.version = Insque::VERSION
18
18
 
19
19
  gem.add_dependency('redis', '> 2')
20
+ gem.add_dependency('json')
21
+ gem.add_development_dependency('rspec')
22
+ gem.add_development_dependency('activesupport')
23
+ gem.add_development_dependency('activerecord')
24
+ gem.add_development_dependency('sqlite3')
20
25
  end
data/insque.local.yml ADDED
@@ -0,0 +1,10 @@
1
+ version: '3.2'
2
+
3
+ services:
4
+ redis:
5
+ image: redis
6
+ ports:
7
+ - target: 6379
8
+ published: 63790
9
+ protocol: tcp
10
+ mode: host
@@ -1,3 +1,3 @@
1
1
  module Insque
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.2"
3
3
  end
data/lib/insque.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require "insque/version"
2
2
  require "redis"
3
+ require 'json'
3
4
  require "insque/railtie" if defined?(Rails)
4
5
 
5
6
  module Insque
6
7
  DEFAULT_INBOX_TTL = 10800 # seconds
8
+ DEFAULT_PROCESSING_TTL = 3600 # seconds
7
9
 
8
10
  def self.inbox_ttl= val
9
11
  @inbox_ttl = val
@@ -13,6 +15,14 @@ module Insque
13
15
  @inbox_ttl || DEFAULT_INBOX_TTL
14
16
  end
15
17
 
18
+ def self.processing_ttl= val
19
+ @processing_ttl = val
20
+ end
21
+
22
+ def self.processing_ttl
23
+ @processing_ttl || DEFAULT_PROCESSING_TTL
24
+ end
25
+
16
26
  def self.debug= debug
17
27
  @debug = debug
18
28
  end
@@ -78,7 +88,7 @@ module Insque
78
88
  end
79
89
 
80
90
  def self.janitor redis=nil
81
- real_janitor @inbox, @processing, (redis || create_redis_connection)
91
+ real_janitor @inbox, @processing, (redis || create_redis_connection), @inbox_pointer
82
92
  end
83
93
 
84
94
  def self.slow_janitor redis=nil
@@ -91,8 +101,8 @@ private
91
101
  loop do
92
102
  redis.setex(pointer, inbox_ttl, inbox) if pointer
93
103
  message = redis.brpoplpush(inbox, processing, 0)
94
- log "#{worker_name} RECEIVING: #{message}" if @debug
95
104
  begin
105
+ log "#{worker_name} RECEIVING: #{message}" if @debug
96
106
  parsed_message = JSON.parse message
97
107
  send(parsed_message['message'], parsed_message)
98
108
  rescue NoMethodError
@@ -100,13 +110,15 @@ private
100
110
  log "#{worker_name} ========== BROKEN_MESSAGE: #{message} =========="
101
111
  log e.inspect
102
112
  log e.backtrace
113
+ ensure
114
+ redis.lrem processing, 0, message
103
115
  end
104
- redis.lrem processing, 0, message
105
116
  end
106
117
  end
107
118
 
108
- def self.real_janitor inbox, processing, redis
119
+ def self.real_janitor inbox, processing, redis, pointer=nil
109
120
  loop do
121
+ redis.setex(pointer, inbox_ttl, inbox) if pointer
110
122
  redis.watch processing
111
123
  errors = []
112
124
  restart = []
@@ -114,15 +126,17 @@ private
114
126
  redis.lrange(processing, 0, -1).each do |m|
115
127
  begin
116
128
  parsed_message = JSON.parse(m)
117
- if parsed_message['restarted_at'] && DateTime.parse(parsed_message['restarted_at']) < 1.hour.ago.utc
129
+ if parsed_message['restarted_at'] && Time.now.to_i - Time.parse(parsed_message['restarted_at']).to_i > processing_ttl
118
130
  errors << parsed_message
119
131
  delete << m
120
- elsif DateTime.parse(parsed_message['broadcasted_at']) < 1.hour.ago.utc
132
+ elsif Time.now.to_i - Time.parse(parsed_message['broadcasted_at']).to_i > processing_ttl
121
133
  restart << parsed_message.merge(restarted_at: Time.now.utc)
122
134
  delete << m
123
135
  end
124
- rescue
136
+ rescue => e
125
137
  log "========== JANITOR_BROKEN_MESSAGE: #{m} =========="
138
+ log e.inspect
139
+ log e.backtrace
126
140
  end
127
141
  end
128
142
  result = redis.multi do |r|
@@ -136,7 +150,7 @@ private
136
150
  else
137
151
  log "CLEANING #{inbox} FAILED"
138
152
  end
139
- sleep(Random.rand * 300)
153
+ sleep(Random.rand((inbox_ttl.to_f / 10).ceil) + 1)
140
154
  end
141
155
  end
142
156
 
@@ -161,10 +175,10 @@ if defined?(ActiveRecord::Base)
161
175
  def send_later(method, *args)
162
176
  Insque.broadcast :send_later, { class: self.class.name, id: id, method: method, args: args }, :slow
163
177
  end
178
+
164
179
  def self.acts_as_insque_crud(*args)
165
180
  options = args.extract_options!
166
181
  excluded = (options[:exclude] || []).map(&:to_s)
167
-
168
182
  set_callback :commit, :after do
169
183
  action = [:create, :update, :destroy].map {|a| a if transaction_include_any_action?([a]) }.compact.first
170
184
  params = self.serializable_hash(options).delete_if {|key| (['created_at', 'updated_at'] + excluded).include? key}
@@ -0,0 +1,110 @@
1
+ require 'redis'
2
+ require 'json'
3
+ require 'active_support/all'
4
+ require 'active_record'
5
+ require 'insque'
6
+
7
+ class User < ActiveRecord::Base
8
+ acts_as_insque_crud
9
+
10
+ def set_status value
11
+ update_column :status, value
12
+ end
13
+ end
14
+
15
+ module Insque
16
+ def self.myapp_test msg
17
+ Insque.redis.set msg['message'], msg['params']['value']
18
+ end
19
+
20
+ def self.myapp_user_update msg
21
+ Insque.redis.set msg['message'], msg['params']['name']
22
+ end
23
+
24
+ def self.myapp_user_create msg
25
+ Insque.redis.set msg['message'], msg['params']['name']
26
+ end
27
+
28
+ def self.myapp_user_destroy msg
29
+ Insque.redis.set msg['message'], msg['params']['name']
30
+ end
31
+ end
32
+
33
+ RSpec.describe 'streamer' do
34
+ before(:all) do
35
+ system "docker swarm init || true"
36
+ system "docker stack deploy -c insque.local.yml insque"
37
+ sleep 10
38
+ Thread.abort_on_exception=true
39
+ Insque.debug = true
40
+ Insque.sender = 'myapp'
41
+ Insque.inbox_ttl = 3
42
+ Insque.redis_config = { host: 'localhost', port: 63790 }
43
+ ActiveRecord::Base.establish_connection(
44
+ :adapter => 'sqlite3',
45
+ :database => 'db/test.db'
46
+ )
47
+ ActiveRecord::Base.connection.execute('create table users(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, status TEXT);')
48
+ end
49
+
50
+ after(:all) do
51
+ system "docker stack rm insque"
52
+ ActiveRecord::Base.connection.execute('drop table users;')
53
+ end
54
+
55
+ before(:each) do
56
+ Insque.redis.flushall
57
+ end
58
+
59
+ it "can broadcast without listeners" do
60
+ Insque.broadcast :test, value: '123'
61
+ end
62
+
63
+ it "sends and recives a message" do
64
+ listener = Thread.new { Insque.listen }
65
+ sleep 1
66
+ expect(Insque.redis.get '{insque}inbox_pointer_myapp').to eq('{insque}inbox_myapp')
67
+ Insque.broadcast :test, value: '123'
68
+ sleep 1
69
+ listener.exit
70
+ expect(Insque.redis.get 'myapp_test').to eq('123')
71
+ expect(Insque.redis.llen '{insque}processing_myapp').to eq(0)
72
+ sleep 4
73
+ expect(Insque.redis.get '{insque}inbox_pointer_myapp').to be_nil
74
+ end
75
+
76
+ it "restarts broken message" do
77
+ janitor = Thread.new { Insque.janitor }
78
+ Insque.redis.lpush('{insque}processing_myapp', { message: 'myapp_test', broadcasted_at: 1.hour.ago }.to_json)
79
+ sleep 3
80
+ expect(Insque.redis.llen '{insque}processing_myapp').to eq(0)
81
+ expect(Insque.redis.llen '{insque}inbox_myapp').to eq(1)
82
+ janitor.exit
83
+ end
84
+
85
+ it "executes model method in background" do
86
+ listener = Thread.new { Insque.slow_listen }
87
+ u = User.create! name: 'Jon Doe'
88
+ u.send_later :set_status, 'processed'
89
+ sleep 1
90
+ listener.exit
91
+ u.reload
92
+ expect(u.status).to eq('processed')
93
+ end
94
+
95
+ it "broadcasts model changes" do
96
+ listener = Thread.new { Insque.listen }
97
+ sleep 1
98
+ u = User.create! name: 'Test'
99
+ sleep 1
100
+ expect(Insque.redis.get 'myapp_user_create').to eq('Test')
101
+ u.name = 'Test2'
102
+ u.save!
103
+ sleep 1
104
+ expect(Insque.redis.get 'myapp_user_update').to eq('Test2')
105
+ u.destroy
106
+ sleep 1
107
+ expect(Insque.redis.get 'myapp_user_destroy').to eq('Test2')
108
+ listener.exit
109
+ end
110
+ end
@@ -0,0 +1,100 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+ RSpec.configure do |config|
17
+ # rspec-expectations config goes here. You can use an alternate
18
+ # assertion/expectation library such as wrong or the stdlib/minitest
19
+ # assertions if you prefer.
20
+ config.expect_with :rspec do |expectations|
21
+ # This option will default to `true` in RSpec 4. It makes the `description`
22
+ # and `failure_message` of custom matchers include text for helper methods
23
+ # defined using `chain`, e.g.:
24
+ # be_bigger_than(2).and_smaller_than(4).description
25
+ # # => "be bigger than 2 and smaller than 4"
26
+ # ...rather than:
27
+ # # => "be bigger than 2"
28
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29
+ end
30
+
31
+ # rspec-mocks config goes here. You can use an alternate test double
32
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
33
+ config.mock_with :rspec do |mocks|
34
+ # Prevents you from mocking or stubbing a method that does not exist on
35
+ # a real object. This is generally recommended, and will default to
36
+ # `true` in RSpec 4.
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
41
+ # have no way to turn it off -- the option exists only for backwards
42
+ # compatibility in RSpec 3). It causes shared context metadata to be
43
+ # inherited by the metadata hash of host groups and examples, rather than
44
+ # triggering implicit auto-inclusion in groups with matching metadata.
45
+ config.shared_context_metadata_behavior = :apply_to_host_groups
46
+
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
+ =begin
50
+ # This allows you to limit a spec run to individual examples or groups
51
+ # you care about by tagging them with `:focus` metadata. When nothing
52
+ # is tagged with `:focus`, all examples get run. RSpec also provides
53
+ # aliases for `it`, `describe`, and `context` that include `:focus`
54
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
55
+ config.filter_run_when_matching :focus
56
+
57
+ # Allows RSpec to persist some state between runs in order to support
58
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
59
+ # you configure your source control system to ignore this file.
60
+ config.example_status_persistence_file_path = "spec/examples.txt"
61
+
62
+ # Limits the available syntax to the non-monkey patched syntax that is
63
+ # recommended. For more details, see:
64
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
65
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
67
+ config.disable_monkey_patching!
68
+
69
+ # This setting enables warnings. It's recommended, but in some cases may
70
+ # be too noisy due to issues in dependencies.
71
+ config.warnings = true
72
+
73
+ # Many RSpec users commonly either run the entire suite or an individual
74
+ # file, and it's useful to allow more verbose output when running an
75
+ # individual spec file.
76
+ if config.files_to_run.one?
77
+ # Use the documentation formatter for detailed output,
78
+ # unless a formatter has already been configured
79
+ # (e.g. via a command-line flag).
80
+ config.default_formatter = "doc"
81
+ end
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ config.profile_examples = 10
87
+
88
+ # Run specs in random order to surface order dependencies. If you find an
89
+ # order dependency and want to debug it, you can fix the order by providing
90
+ # the seed, which is printed after each run.
91
+ # --seed 1234
92
+ config.order = :random
93
+
94
+ # Seed global randomization in this process using the `--seed` CLI option.
95
+ # Setting this allows you to use `--seed` to deterministically reproduce
96
+ # test failures related to randomization by passing the same `--seed` value
97
+ # as the one that triggered the failure.
98
+ Kernel.srand config.seed
99
+ =end
100
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: insque
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Gomozov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-13 00:00:00.000000000 Z
11
+ date: 2018-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -24,6 +24,76 @@ dependencies:
24
24
  - - ">"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
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'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
27
97
  description: Instant queue. Background processing and message driven communication
28
98
  tool. Faster and simplier alternative to Resque.
29
99
  email:
@@ -38,6 +108,7 @@ files:
38
108
  - README.md
39
109
  - Rakefile
40
110
  - insque.gemspec
111
+ - insque.local.yml
41
112
  - lib/generators/insque/initializer_generator.rb
42
113
  - lib/generators/insque/templates/insque.erb
43
114
  - lib/generators/insque/templates/redis.yml
@@ -45,6 +116,8 @@ files:
45
116
  - lib/insque/railtie.rb
46
117
  - lib/insque/version.rb
47
118
  - lib/tasks/insque.rake
119
+ - spec/insque_spec.rb
120
+ - spec/spec_helper.rb
48
121
  homepage: https://github.com/Gropher/insque
49
122
  licenses:
50
123
  - MIT
@@ -69,4 +142,6 @@ rubygems_version: 2.5.1
69
142
  signing_key:
70
143
  specification_version: 4
71
144
  summary: Redis-based multi-threaded queue
72
- test_files: []
145
+ test_files:
146
+ - spec/insque_spec.rb
147
+ - spec/spec_helper.rb