sidetiq 0.3.7 → 0.4.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +2 -2
  5. data/README.md +9 -16
  6. data/Rakefile +2 -7
  7. data/examples/Procfile +2 -0
  8. data/examples/config.ru +15 -0
  9. data/examples/server.rb +17 -0
  10. data/examples/workers/failing.rb +13 -0
  11. data/examples/workers/simple.rb +10 -0
  12. data/lib/sidetiq/actor/clock.rb +31 -0
  13. data/lib/sidetiq/actor/handler.rb +7 -0
  14. data/lib/sidetiq/actor.rb +38 -0
  15. data/lib/sidetiq/api.rb +109 -0
  16. data/lib/sidetiq/clock.rb +3 -118
  17. data/lib/sidetiq/config.rb +8 -0
  18. data/lib/sidetiq/handler.rb +50 -0
  19. data/lib/sidetiq/lock/meta_data.rb +53 -0
  20. data/lib/sidetiq/lock/redis.rb +115 -0
  21. data/lib/sidetiq/lock/watcher.rb +41 -0
  22. data/lib/sidetiq/logging.rb +12 -0
  23. data/lib/sidetiq/middleware/history.rb +58 -0
  24. data/lib/sidetiq/schedulable.rb +6 -7
  25. data/lib/sidetiq/supervisor.rb +50 -0
  26. data/lib/sidetiq/version.rb +6 -3
  27. data/lib/sidetiq/views/_home_nav.erb +16 -0
  28. data/lib/sidetiq/views/_worker_nav.erb +22 -0
  29. data/lib/sidetiq/views/assets/styles.css +36 -0
  30. data/lib/sidetiq/views/history.erb +48 -0
  31. data/lib/sidetiq/views/locks.erb +48 -0
  32. data/lib/sidetiq/views/schedule.erb +59 -0
  33. data/lib/sidetiq/views/sidetiq.erb +39 -28
  34. data/lib/sidetiq/web.rb +36 -16
  35. data/lib/sidetiq.rb +34 -114
  36. data/sidetiq.gemspec +5 -2
  37. data/tasks/bundler.task +1 -0
  38. data/tasks/minitest.task +6 -0
  39. data/test/fixtures/optional_arguments_worker.rb +8 -0
  40. data/test/helper.rb +21 -6
  41. data/test/test_clock.rb +16 -24
  42. data/test/test_history.rb +60 -0
  43. data/test/test_lock_meta_data.rb +90 -0
  44. data/test/test_lock_redis.rb +63 -0
  45. data/test/test_version.rb +2 -1
  46. data/test/test_watcher.rb +24 -0
  47. data/test/test_web.rb +26 -2
  48. metadata +80 -12
  49. data/examples/simple.rb +0 -22
  50. data/lib/sidetiq/lock.rb +0 -63
  51. data/lib/sidetiq/middleware.rb +0 -23
  52. data/lib/sidetiq/views/sidetiq_details.erb +0 -49
  53. data/test/test_lock.rb +0 -30
  54. data/test/test_middleware.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd23a1f597a8ea9f38f1a7d978c25aacb9881278
4
- data.tar.gz: 38857c20bd446738e877befb5357da050d317f7b
3
+ metadata.gz: afa72994a97058431570f5d871d813841987f3d3
4
+ data.tar.gz: 481d72a325c1c96c24cd0f0f38a346fc85077abf
5
5
  SHA512:
6
- metadata.gz: 4f3b2ac3341c8a3fa3f4b38ca192a8ff234f066d2fae8ebd2b0012393ca0fc418da7b64603caec2bf1aabf8fbed61200326fda4c74d0b6c3ac1422fa0fbc6c6f
7
- data.tar.gz: 6024d079bea833aeef1d2b803bb32ed2f2dfe9108333bdaa6a9558ab28a07c3b67c52a38f5f34980ed4de29c4f988b50f78ed87a82b79481b3f57330f900639f
6
+ metadata.gz: ea02330ee1cceb86a386b2d47bb2b825462e549fbf236f411bf08c9b55172ec4d7073e5eaab6b9ebbabff1c2280c330ad0247014bcbcc5625ed6d4651ab0430f
7
+ data.tar.gz: 5989c73b9f8e9c203b18ffa22dcedc2f92e06eeceafa88f6e301e08a0d8c631d6ceeb6b47fa747fc85c528711208a67e4a6d29b53993e0df04ca342271a269d3
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
+ script: rake test
2
3
  branches:
3
4
  only:
4
5
  - master
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ 0.4.0
2
+ -----
3
+
4
+ - Show job history in web extension.
5
+ - Integrate with Sidekiq's exception handling/reporting in critical parts.
6
+ - Store more detailed lock metadata.
7
+ - Remove stray 'thead' from ERB template.
8
+ - Store scheduled worker history in Redis.
9
+ - Use a Celluloid pool of scheduling handlers to run calculations in parallel.
10
+ - Use Celluloid actors instead of plain threads.
11
+ - Fix to work with workers with one optional argument. [nata79]
12
+ - Refactor top-level namespace methods into separate modules.
13
+ - Add Procfile-based example code to boot Sidekiq and the web frontend
14
+ simultaneuously.
15
+ - Experimental watcher worker to remove invalid locks.
16
+
1
17
  0.3.7
2
18
  -----
3
19
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2013, Tobias Svensson <tobias@musicglue.com>
1
+ Copyright (c) 2012-2013, Tobias Svensson <tob@tobiassvensson.co.uk>
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -21,4 +21,4 @@ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
21
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
22
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
23
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md CHANGED
@@ -32,9 +32,11 @@ Overview
32
32
 
33
33
  Sidetiq provides a simple API for defining recurring workers for Sidekiq.
34
34
 
35
+ - Cuncurrency using Celluloid actors.
36
+
35
37
  - Flexible DSL based on [ice_cube](http://seejohnrun.github.com/ice_cube/)
36
38
 
37
- - Sidetiq uses a locking mechanism (based on `setnx` and `pexpire`) internally
39
+ - Sidetiq uses a locking mechanism (based on watch/multi/psetex/exec) internally
38
40
  so Sidetiq clocks can run in each Sidekiq process without interfering with
39
41
  each other (tested with sub-second polling of scheduled jobs by Sidekiq and
40
42
  Sidetiq clock rates above 100hz).
@@ -46,6 +48,7 @@ Dependencies
46
48
  ------------
47
49
 
48
50
  - [Sidekiq](http://mperham.github.com/sidekiq/)
51
+ - [Celluloid](http://celluloid.io/) (shared with Sidekiq)
49
52
  - [ice_cube](http://seejohnrun.github.com/ice_cube/)
50
53
 
51
54
  <a name='section_Installation'></a>
@@ -136,17 +139,8 @@ class MyWorker
136
139
  end
137
140
  ```
138
141
 
139
- To start Sidetiq, simply call `Sidetiq::Clock.start!` in a server specific
140
- configuration block:
141
-
142
- ```ruby
143
- Sidekiq.configure_server do |config|
144
- Sidetiq::Clock.start!
145
- end
146
- ```
147
-
148
- Additionally, Sidetiq includes a middleware that will check if the clock
149
- thread is still alive and restart it if necessary.
142
+ If Sidekiq is running in server-mode, Sidekiq will handle recurring
143
+ jobs automatically.
150
144
 
151
145
  <a name='section_Backfills''></a>
152
146
  Backfills
@@ -180,10 +174,6 @@ Configuration
180
174
 
181
175
  ```ruby
182
176
  Sidetiq.configure do |config|
183
- # Thread priority of the clock thread (default: Thread.main.priority as
184
- # defined when Sidetiq is loaded).
185
- config.priority = 2
186
-
187
177
  # Clock tick resolution in seconds (default: 1).
188
178
  config.resolution = 0.5
189
179
 
@@ -192,6 +182,9 @@ Sidetiq.configure do |config|
192
182
 
193
183
  # When `true` uses UTC instead of local times (default: false)
194
184
  config.utc = false
185
+
186
+ # Scheduling handler pool size (default: number of CPUs)
187
+ config.handler_pool_size = 5
195
188
  end
196
189
  ```
197
190
  <a name='section_Configuration_Logging'></a>
data/Rakefile CHANGED
@@ -1,8 +1,3 @@
1
- require 'bundler/gem_tasks'
2
- require 'rake/testtask'
1
+ Dir["tasks/**/*.task"].each { |task| load task }
3
2
 
4
- Rake::TestTask.new do |t|
5
- t.pattern = 'test/**/test_*.rb'
6
- end
7
-
8
- task default: :test
3
+ task :default => :test
data/examples/Procfile ADDED
@@ -0,0 +1,2 @@
1
+ sidekiq: bundle exec sidekiq -r ./server.rb
2
+ web: bundle exec rackup
@@ -0,0 +1,15 @@
1
+ require 'sidekiq'
2
+ require 'sidetiq'
3
+
4
+ require 'sidekiq/web'
5
+ require 'sidetiq/web'
6
+ require 'sidetiq/lock/watcher'
7
+
8
+ require './workers/simple.rb'
9
+ require './workers/failing.rb'
10
+
11
+ Sidekiq.configure_client do |config|
12
+ config.redis = { :size => 1 }
13
+ end
14
+
15
+ run Sidekiq::Web
@@ -0,0 +1,17 @@
1
+ # Run with `sidekiq -r /path/to/simple.rb`
2
+
3
+ require 'sidekiq'
4
+ require 'sidetiq'
5
+ require 'sidetiq/lock/watcher'
6
+
7
+ require_relative 'workers/simple'
8
+ require_relative 'workers/failing'
9
+
10
+ Sidekiq.logger.level = Logger::DEBUG
11
+
12
+ Sidekiq.options[:poll_interval] = 1
13
+
14
+ Sidekiq.configure_server do |config|
15
+ Sidetiq.clock.start!
16
+ end
17
+
@@ -0,0 +1,13 @@
1
+ class Failing
2
+ include Sidekiq::Worker
3
+ include Sidetiq::Schedulable
4
+
5
+ recurrence { secondly }
6
+
7
+ def perform(*args)
8
+ if rand(5) == 0
9
+ raise "This didn't go well."
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,10 @@
1
+ class Simple
2
+ include Sidekiq::Worker
3
+ include Sidetiq::Schedulable
4
+
5
+ recurrence { secondly }
6
+
7
+ def perform(*args)
8
+ end
9
+ end
10
+
@@ -0,0 +1,31 @@
1
+ module Sidetiq
2
+ module Actor
3
+ class Clock < Sidetiq::Clock
4
+ include Sidetiq::Actor
5
+ include Sidekiq::ExceptionHandler
6
+
7
+ # Public: Starts the clock loop.
8
+ def start!
9
+ debug "Sidetiq::Clock looping ..."
10
+ loop!
11
+ end
12
+
13
+ private
14
+
15
+ def loop!
16
+ after([time { tick }, 0].max) do
17
+ loop!
18
+ end
19
+ rescue StandardError => e
20
+ handle_exception(e, context: 'Sidetiq::Clock#loop!')
21
+ retry
22
+ end
23
+
24
+ def time
25
+ start = gettime
26
+ yield
27
+ Sidetiq.config.resolution - (gettime.to_f - start.to_f)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module Sidetiq
2
+ module Actor
3
+ class Handler < Sidetiq::Handler
4
+ include Sidetiq::Actor
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module Sidetiq
2
+ module Actor
3
+ def self.included(base)
4
+ base.__send__(:include, Celluloid)
5
+ base.finalizer :sidetiq_finalizer
6
+ end
7
+
8
+ def initialize(*args, &block)
9
+ log_call "initialize"
10
+
11
+ super
12
+
13
+ # Link to Sidekiq::Manager when running in server-mode. In most
14
+ # cases the supervisor is booted before Sidekiq has launched
15
+ # fully, so defer this.
16
+ if Sidekiq.server?
17
+ after(0.1) { link_to_sidekiq_manager }
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def sidetiq_finalizer
24
+ log_call "shutting down ..."
25
+ end
26
+
27
+ def link_to_sidekiq_manager
28
+ Sidekiq::CLI.instance.launcher.manager.link(current_actor)
29
+ rescue NoMethodError
30
+ warn "Can't link #{self.class.name}. Sidekiq::Manager not running. Retrying ..."
31
+ after(1) { link_to_sidekiq_manager }
32
+ end
33
+
34
+ def log_call(call)
35
+ info "#{self.class.name} id: #{object_id} #{call}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,109 @@
1
+ module Sidetiq
2
+ # Public: Sidetiq API methods.
3
+ module API
4
+ # Public: Returns an Array of workers including Sidetiq::Schedulable.
5
+ def workers
6
+ schedules.keys
7
+ end
8
+
9
+ # Public: Returns a Hash of Sidetiq::Schedule instances.
10
+ def schedules
11
+ clock.schedules.dup
12
+ end
13
+
14
+ # Public: Currently scheduled recurring jobs.
15
+ #
16
+ # worker - A Sidekiq::Worker class or String of the class name (optional)
17
+ # block - An optional block that can be given to which each
18
+ # Sidekiq::SortedEntry instance corresponding to a scheduled job will
19
+ # be yielded.
20
+ #
21
+ # Examples
22
+ #
23
+ # Sidetiq.scheduled
24
+ # # => [#<Sidekiq::SortedEntry>, ...]
25
+ #
26
+ # Sidetiq.scheduled(MyWorker)
27
+ # # => [#<Sidekiq::SortedEntry>, ...]
28
+ #
29
+ # Sidetiq.scheduled("MyWorker")
30
+ # # => [#<Sidekiq::SortedEntry>, ...]
31
+ #
32
+ # Sidetiq.scheduled do |job|
33
+ # # do stuff ...
34
+ # end
35
+ # # => [#<Sidekiq::SortedEntry>, ...]
36
+ #
37
+ # Sidetiq.scheduled(MyWorker) do |job|
38
+ # # do stuff ...
39
+ # end
40
+ # # => [#<Sidekiq::SortedEntry>, ...]
41
+ #
42
+ # Sidetiq.scheduled("MyWorker") do |job|
43
+ # # do stuff ...
44
+ # end
45
+ # # => [#<Sidekiq::SortedEntry>, ...]
46
+ #
47
+ # Yields each Sidekiq::SortedEntry instance.
48
+ # Returns an Array of Sidekiq::SortedEntry objects.
49
+ def scheduled(worker = nil, &block)
50
+ filter_set(Sidekiq::ScheduledSet.new, worker, &block)
51
+ end
52
+
53
+ # Public: Recurring jobs currently scheduled for retries.
54
+ #
55
+ # worker - A Sidekiq::Worker class or String of the class name (optional)
56
+ # block - An optional block that can be given to which each
57
+ # Sidekiq::SortedEntry instance corresponding to a scheduled job will
58
+ # be yielded.
59
+ #
60
+ # Examples
61
+ #
62
+ # Sidetiq.retries
63
+ # # => [#<Sidekiq::SortedEntry>, ...]
64
+ #
65
+ # Sidetiq.retries(MyWorker)
66
+ # # => [#<Sidekiq::SortedEntry>, ...]
67
+ #
68
+ # Sidetiq.retries("MyWorker")
69
+ # # => [#<Sidekiq::SortedEntry>, ...]
70
+ #
71
+ # Sidetiq.retries do |job|
72
+ # # do stuff ...
73
+ # end
74
+ # # => [#<Sidekiq::SortedEntry>, ...]
75
+ #
76
+ # Sidetiq.retries(MyWorker) do |job|
77
+ # # do stuff ...
78
+ # end
79
+ # # => [#<Sidekiq::SortedEntry>, ...]
80
+ #
81
+ # Sidetiq.retries("MyWorker") do |job|
82
+ # # do stuff ...
83
+ # end
84
+ # # => [#<Sidekiq::SortedEntry>, ...]
85
+ #
86
+ # Yields each Sidekiq::SortedEntry instance.
87
+ # Returns an Array of Sidekiq::SortedEntry objects.
88
+ def retries(worker = nil, &block)
89
+ filter_set(Sidekiq::RetrySet.new, worker, &block)
90
+ end
91
+
92
+ private
93
+
94
+ def filter_set(set, worker, &block)
95
+ worker = worker.constantize if worker.kind_of?(String)
96
+
97
+ jobs = set.select do |job|
98
+ klass = job.klass.constantize
99
+ ret = klass.include?(Schedulable)
100
+ ret = ret && klass == worker if worker
101
+ ret
102
+ end
103
+
104
+ jobs.each(&block) if block_given?
105
+
106
+ jobs
107
+ end
108
+ end
109
+ end
data/lib/sidetiq/clock.rb CHANGED
@@ -1,26 +1,11 @@
1
1
  module Sidetiq
2
- configure do |config|
3
- config.priority = Thread.main.priority
4
- config.resolution = 1
5
- config.lock_expire = 1000
6
- config.utc = false
7
- end
8
-
9
2
  # Public: The Sidetiq clock.
10
3
  class Clock
11
- include Singleton
12
- include MonitorMixin
4
+ include Logging
13
5
 
14
6
  # Internal: Returns a hash of Sidetiq::Schedule instances.
15
7
  attr_reader :schedules
16
8
 
17
- # Internal: Returns the clock thread.
18
- attr_reader :thread
19
-
20
- def self.method_missing(meth, *args, &block) # :nodoc:
21
- instance.__send__(meth, *args, &block)
22
- end
23
-
24
9
  def initialize # :nodoc:
25
10
  super
26
11
  @schedules = {}
@@ -50,18 +35,8 @@ module Sidetiq
50
35
  # Returns a hash of Sidetiq::Schedule instances.
51
36
  def tick
52
37
  tick = gettime
53
- mon_synchronize do
54
- schedules.each do |worker, sched|
55
- Lock.new(worker).synchronize do |redis|
56
- if sched.backfill? && (last = worker.last_scheduled_occurrence) > 0
57
- last = Sidetiq.config.utc ? Time.at(last).utc : Time.at(last)
58
- sched.occurrences_between(last + 1, tick).each do |past_t|
59
- enqueue(worker, past_t, redis)
60
- end
61
- end
62
- enqueue(worker, sched.next_occurrence(tick), redis)
63
- end if sched.schedule_next?(tick)
64
- end
38
+ schedules.each do |worker, sched|
39
+ Sidetiq.handler.dispatch(worker,sched, tick)
65
40
  end
66
41
  end
67
42
 
@@ -76,96 +51,6 @@ module Sidetiq
76
51
  def gettime
77
52
  Sidetiq.config.utc ? Time.now.utc : Time.now
78
53
  end
79
-
80
- # Public: Starts the clock unless it is already running.
81
- #
82
- # Examples
83
- #
84
- # start!
85
- # # => Thread
86
- #
87
- # Returns the Thread instance of the clock thread.
88
- def start!
89
- return if ticking?
90
-
91
- Sidetiq.logger.info "Sidetiq::Clock start"
92
-
93
- @thread = Thread.start { clock { tick } }
94
- @thread.abort_on_exception = true
95
- @thread.priority = Sidetiq.config.priority
96
- @thread
97
- end
98
-
99
- # Public: Stops the clock if it is running.
100
- #
101
- # Examples
102
- #
103
- # stop!
104
- # # => nil
105
- #
106
- # Returns nil.
107
- def stop!
108
- if ticking?
109
- @thread.kill
110
- Sidetiq.logger.info "Sidetiq::Clock stop"
111
- end
112
- end
113
-
114
- # Public: Returns the status of the clock.
115
- #
116
- # Examples
117
- #
118
- # ticking?
119
- # # => false
120
- #
121
- # start!
122
- # ticking?
123
- # # => true
124
- #
125
- # Returns true or false.
126
- def ticking?
127
- @thread && @thread.alive?
128
- end
129
-
130
- private
131
-
132
- def enqueue(worker, time, redis)
133
- key = "sidetiq:#{worker.name}"
134
- time_f = time.to_f
135
- next_run = (redis.get("#{key}:next") || -1).to_f
136
-
137
- if next_run < time_f
138
- Sidetiq.logger.info "Sidetiq::Clock enqueue #{worker.name} (at: #{time_f}) (last: #{next_run})"
139
-
140
- redis.mset("#{key}:last", next_run, "#{key}:next", time_f)
141
-
142
- case worker.instance_method(:perform).arity
143
- when 0
144
- worker.perform_at(time)
145
- when 1
146
- worker.perform_at(time, next_run)
147
- else
148
- worker.perform_at(time, next_run, time_f)
149
- end
150
- end
151
- end
152
-
153
- def clock
154
- loop do
155
- sleep_time = time { yield }
156
-
157
- if sleep_time > 0
158
- Thread.pass
159
- sleep sleep_time
160
- end
161
- end
162
- end
163
-
164
- def time
165
- start = gettime
166
- yield
167
- Sidetiq.config.resolution - (gettime.to_f - start.to_f)
168
- end
169
54
  end
170
55
  end
171
56
 
@@ -22,5 +22,13 @@ module Sidetiq
22
22
  @config ||= OpenStruct.new
23
23
  end
24
24
  end
25
+
26
+ configure do |config|
27
+ config.worker_history = 50
28
+ config.resolution = 1
29
+ config.lock_expire = 1000
30
+ config.utc = false
31
+ config.handler_pool_size = nil
32
+ end
25
33
  end
26
34
 
@@ -0,0 +1,50 @@
1
+ module Sidetiq
2
+ class Handler
3
+ include Logging
4
+ include Sidekiq::ExceptionHandler
5
+
6
+ def dispatch(worker, sched, tick)
7
+ return unless sched.schedule_next?(tick)
8
+
9
+ Lock::Redis.new(worker).synchronize do |redis|
10
+ if sched.backfill? && (last = worker.last_scheduled_occurrence) > 0
11
+ last = Sidetiq.config.utc ? Time.at(last).utc : Time.at(last)
12
+ sched.occurrences_between(last + 1, tick).each do |past_t|
13
+ enqueue(worker, past_t, redis)
14
+ end
15
+ end
16
+
17
+ enqueue(worker, sched.next_occurrence(tick), redis)
18
+ end
19
+ rescue StandardError => e
20
+ handle_exception(e, context: "Sidetiq::Handler#dispatch")
21
+ raise e
22
+ end
23
+
24
+ private
25
+
26
+ def enqueue(worker, time, redis)
27
+ key = "sidetiq:#{worker.name}"
28
+ time_f = time.to_f
29
+ next_run = (redis.get("#{key}:next") || -1).to_f
30
+
31
+ if next_run < time_f
32
+ info "Enqueue: #{worker.name} (at: #{time_f}) (last: #{next_run})"
33
+
34
+ redis.mset("#{key}:last", next_run, "#{key}:next", time_f)
35
+
36
+ case worker.instance_method(:perform).arity.abs
37
+ when 0
38
+ worker.perform_at(time)
39
+ when 1
40
+ worker.perform_at(time, next_run)
41
+ else
42
+ worker.perform_at(time, next_run, time_f)
43
+ end
44
+ end
45
+ rescue StandardError => e
46
+ handle_exception(e, context: "Sidetiq::Handler#enqueue")
47
+ raise e
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ module Sidetiq
2
+ module Lock
3
+ class MetaData
4
+ OWNER = "#{Socket.gethostname}:#{Process.pid}"
5
+
6
+ attr_accessor :owner, :timestamp, :key
7
+
8
+ class << self
9
+ include Sidekiq::ExceptionHandler
10
+
11
+ def for_new_lock(key)
12
+ new(owner: OWNER, timestamp: Sidetiq.clock.gettime.to_f, key: key)
13
+ end
14
+
15
+ def from_json(json = "")
16
+ # Avoid TypeError when nil is passed to JSON.parse.
17
+ json = "" if json.nil?
18
+
19
+ hash = JSON.parse(json, symbolize_names: true)
20
+ new(hash)
21
+ rescue JSON::ParserError => e
22
+ if json != ""
23
+ # Looks like garbage lock metadata, so report it.
24
+ handle_exception(e, context: "Garbage lock meta data detected: #{json}")
25
+ end
26
+
27
+ new
28
+ end
29
+ end
30
+
31
+ def initialize(hash = {})
32
+ @owner = hash[:owner]
33
+ @timestamp = hash[:timestamp]
34
+ @key = hash[:key]
35
+ end
36
+
37
+ def pttl
38
+ Sidekiq.redis { |r| r.pttl(key) }
39
+ end
40
+
41
+ def to_json
42
+ instance_variables.each_with_object({}) do |var, hash|
43
+ hash[var.to_s.delete("@")] = instance_variable_get(var)
44
+ end.to_json
45
+ end
46
+
47
+ def to_s
48
+ "Sidetiq::Lock on #{key} set at #{timestamp} by #{owner}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+