loop_hard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0699c1f2cb84e6b558a558c3b5c64c7b2f4be7df
4
+ data.tar.gz: 2287abde57b5ed965765ec8bd15612a04e694189
5
+ SHA512:
6
+ metadata.gz: 43b6dec2d6259200b72f64d57751d3d37088843a734f0276d27a5e4d44953cff4b8d47de0056bf311e95942203d8e831a0edbd073948730e8b1928b7c1b14e9c
7
+ data.tar.gz: 0ab323b7a1402cd7c79c944a5059fa49cdbeef2521e3842749843516b7d427f5debe7d85f1440281474d5341f2e56f8f3c489a270e5a190f364c5afcfd130b5b
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ .idea
3
+ *.gem
4
+ coverage
5
+ Gemfile.lock
6
+ .yardoc
7
+ doc
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.1.1"
4
+ - "2.1.5"
5
+ - "2.2.2"
6
+
7
+ script: bundle exec rake test_with_coveralls
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in loop_hard.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Daniel Magliola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,130 @@
1
+ # loop_hard
2
+
3
+ [![Build Status](https://travis-ci.org/dmagliola/loop_hard.svg?branch=master)](https://travis-ci.org/dmagliola/loop_hard)
4
+ [![Coverage Status](https://coveralls.io/repos/dmagliola/loop_hard/badge.svg?branch=master&service=github)](https://coveralls.io/github/dmagliola/loop_hard?branch=master)
5
+ [![Code Climate](https://codeclimate.com/github/dmagliola/loop_hard/badges/gpa.svg)](https://codeclimate.com/github/dmagliola/loop_hard)
6
+ [![Inline docs](http://inch-ci.org/github/dmagliola/loop_hard.svg?branch=master&style=flat)](http://inch-ci.org/github/dmagliola/loop_hard)
7
+ [![Gem Version](https://badge.fury.io/rb/loop_hard.png)](http://badge.fury.io/rb/loop_hard)
8
+
9
+ Add timeouts to your long-running loops, and response to signals gracefully.
10
+
11
+ LoopHard allows you to have long-running worker loops that will stop after a while,
12
+ and also stop if they get an external signal to stop (for example, if Sidekiq stops due to a USR1,
13
+ or a TERM signal is trapped).
14
+
15
+ Possible use cases:
16
+
17
+ - You have a background job that runs every 10 minutes and loops through a bunch of records until there are none left.
18
+ Use `LoopHard.loop(timeout: 9.5.minutes)` to generally prevent overlapping.
19
+ - You don't have a time limit, but you're running a long job inside Sidekiq, and you want to exit gracefully when
20
+ Sidekiq decides it's terminating (and before Heroku kills the process!).
21
+ You shouldn't have long-running jobs on Sidekiq, but hey! it happens!
22
+ - Same case as before, but not inside Sidekiq. You're either handling signals yourself, or have some other
23
+ library (in which case, please do a PR!)
24
+
25
+ The assumption is, obviously, that your loop will run for a long time, but each iteration of your loop is going to be
26
+ quick, otherwise, we can't really exit gracefully. But if we do exit gracefully, you know you're exiting in a known state.
27
+
28
+ ## Download
29
+
30
+ Gem:
31
+
32
+ `gem install loop_hard`
33
+
34
+ ## Installation
35
+
36
+ Load the gem in your GemFile.
37
+
38
+ `gem "loop_hard"`
39
+
40
+
41
+ ## Loop Hard!
42
+
43
+ Replace your:
44
+
45
+ ```
46
+ loop do
47
+ # do stuff until we break
48
+ end
49
+ ```
50
+
51
+ with
52
+
53
+ ```
54
+ LoopHard.loop(timeout: 10.minutes) do
55
+ # do stuff until we break, or until 10 minutes go by, or until something tells us to stop
56
+ end
57
+ ```
58
+
59
+ or, don't really specify a timeout if you're going to run forever, but just want to trap signals / Sidekiq shutdown
60
+ gracefully.
61
+
62
+ ### Set your Logger
63
+
64
+ LoopHard logs to LoopHard.logger every time it exits a loop, since normally these aren't normal conditions and you
65
+ might want to be notified about it. By default, it logs to stdout.
66
+
67
+ You probably want to set it to `LoopHard.logger = Rails.logger`, or whatever logger you're using in your app.
68
+
69
+
70
+
71
+ ### Trap your own signals
72
+
73
+ If you want LoopHard to trap signals itself, you'll need to call `LoopHard::SignalTrap.trap_signals`
74
+
75
+ By default it'll trap INT, TERM and USR1, but you can pass the signals you'd like trapped
76
+ as a parameter to `trap_signals`.
77
+
78
+ Only do this if you are sure nothing else in your app is trapping signals, because there can only be one signal handler
79
+ per process. For example, if you are using Siekiq, **do not do this**, or you will get in the way of the Sidekiq shutdown
80
+ process.
81
+
82
+ If you are already trapping signals yourself, you can call `LoopHard::SignalTrap.signal_trapped` when you trap a signal
83
+ that should stop your loops.
84
+
85
+
86
+ ## Version Compatibility and Continuous Integration
87
+
88
+ Tested with [Travis](https://travis-ci.org/dmagliola/loop_hard) using Ruby 2.1.1, 2.1.5 and 2.2.2.
89
+
90
+ LoopHard does work with Ruby 1.9, however, Sidekiq doesn't, so the Sidekiq trap won't work, but the rest will.
91
+
92
+ To locally run tests do:
93
+
94
+ ```
95
+ rake test
96
+ ```
97
+
98
+ ## Copyright
99
+
100
+ Copyright (c) 2015, Daniel Magliola
101
+
102
+ See LICENSE for details.
103
+
104
+
105
+ ## Users
106
+
107
+ This gem is being used by:
108
+
109
+ - [MSTY](https://www.msty.com)
110
+ - You? please, let us know if you are using this gem.
111
+
112
+
113
+ ## Changelog
114
+
115
+ ### Version 0.1.0 (Oct 20th, 2015)
116
+ - Newly released gem
117
+
118
+ ## Contributing
119
+
120
+ 1. Fork it
121
+ 1. Create your feature branch (`git checkout -b my-new-feature`)
122
+ 1. Code your thing
123
+ 1. Write and run tests:
124
+ bundle install
125
+ rake test
126
+ 1. Write documentation and make sure it looks good: yard server --reload
127
+ 1. Add items to the changelog, in README.
128
+ 1. Commit your changes (`git commit -am "Add some feature"`)
129
+ 1. Push to the branch (`git push origin my-new-feature`)
130
+ 1. Create new Pull Request
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rake/testtask"
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "lib"
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ end
10
+
11
+ task :default => :test
12
+
13
+ require 'coveralls/rake/task'
14
+ Coveralls::RakeTask.new
15
+ task :test_with_coveralls => ["test", "coveralls:push"]
@@ -0,0 +1,11 @@
1
+ require 'logger'
2
+ require 'loop_hard/logger'
3
+ require 'loop_hard/loop'
4
+ require 'loop_hard/sidekiq_trap'
5
+ require 'loop_hard/signal_trap'
6
+ require 'loop_hard/timeout_trap'
7
+ require 'loop_hard/version'
8
+
9
+ # Have loops with a timeout, that listen to signals to know if they should stop prematurely
10
+ module LoopHard
11
+ end
@@ -0,0 +1,10 @@
1
+ module LoopHard
2
+ class << self
3
+ attr_writer :logger
4
+
5
+ # Set the Logger for LoopHard
6
+ def logger
7
+ @logger ||= Logger.new($stdout)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module LoopHard
2
+ class << self
3
+ # Loop until the passed in block breaks, or until one of the traps open.
4
+ # Will yield to the block passed in until it decides to stop looping.
5
+ # Your block should break once it's done.
6
+ # @param options
7
+ # - timeout [Int] Maximum time to run (eg: "10" for 10 seconds, 10.minutes if you are using Rails). Defaults to infinite
8
+ # - traps [Array of Classes] Which traps to run. Defaults to [SidekiqTrap, SignalTrap, TimeoutTrap] (all of them)
9
+ def loop(options = {})
10
+ default_options = { timeout: nil,
11
+ traps: [SidekiqTrap, SignalTrap, TimeoutTrap] }
12
+ options = default_options.merge(options)
13
+
14
+ options[:maximum_end_time] = (options[:timeout] ? Time.now + options[:timeout] : nil)
15
+
16
+ while continue_looping?(options)
17
+ yield # Block will call "break" once it's out of rows, which will break this loop
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ # Decide whether to continue looping, based on the traps involved.
24
+ def continue_looping?(options)
25
+ options[:traps].all?{|trap| trap.continue?(options)}
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module LoopHard
2
+ # If Sidekiq has decided to not process more jobs, then stop looping, to let this job end.
3
+ # This is a massive hack, and we totally shouldn't be doing this (as per mperham himself!)
4
+ # https://github.com/mperham/sidekiq/issues/2364
5
+ # But it's better than an ungraceful shutdown
6
+ # Also, we can't trap the signals ourselves, since Sidekiq is trapping them, and you can only have one "trap"
7
+ module SidekiqTrap
8
+ class << self
9
+
10
+ # Returns false if Sidekiq has decided to stop taking new jobs. True otherwise.
11
+ def continue?(options = nil)
12
+ if defined?(Sidekiq) && Sidekiq.server? && defined?(Sidekiq::Fetcher) && Sidekiq::Fetcher.done?
13
+ LoopHard.logger.info "Ending loop due to Sidekiq shutting down"
14
+ return false
15
+ end
16
+ return true
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module LoopHard
2
+ module SignalTrap
3
+ class << self
4
+ @signal_trapped = nil
5
+
6
+ # Returns false if a signal has been trapped. True otherwise.
7
+ def continue?(options = nil)
8
+ if !@signal_trapped.nil?
9
+ LoopHard.logger.info "Ending loop due to #{@signal_trapped} signal"
10
+ return false
11
+ end
12
+ return true
13
+ end
14
+
15
+ # Set up a signal trap for the signals specified (defaults to INT, TERM and USR1)
16
+ # Do not call this if you're using Sidekiq or any other library that handles their own signals!
17
+ def trap_signals(signals = ["INT", "TERM", "USR1"])
18
+ signals.each do |sig|
19
+ Signal.trap sig do
20
+ signal_trapped(sig)
21
+ end
22
+ end
23
+ end
24
+
25
+ # Set the "signal" flag, if you've trapped a signal yourself, so that loops stop looping.
26
+ def signal_trapped(sig)
27
+ @signal_trapped = sig
28
+ end
29
+
30
+ # Reset the "signal" flag, so that loops keep looping.
31
+ def reset_signal_trapped
32
+ @signal_trapped = nil
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ module LoopHard
2
+ module TimeoutTrap
3
+ class << self
4
+
5
+ # Returns false if the timeout has expired. True otherwise.
6
+ # Expects an options has with a `maximum_end_time` key.
7
+ def continue?(options)
8
+ if options[:maximum_end_time] && Time.now > options[:maximum_end_time]
9
+ LoopHard.logger.info "Ending loop due to timeout"
10
+ return false
11
+ end
12
+ return true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module LoopHard
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,40 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "loop_hard/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'loop_hard'
7
+ s.version = LoopHard::VERSION
8
+ s.summary = "Have long-running loops with a timeout, that listen to signals to know if they should stop prematurely"
9
+ s.description = %q{LoopHard allows you to have long-running worker loops that will stop after a while,
10
+ and also stop if they get an external signal to stop (for example, if Sidekiq stops due to a USR1,
11
+ or a KILL signal is trapped.
12
+ }
13
+ s.authors = ["Daniel Magliola"]
14
+ s.email = 'dmagliola@crystalgears.com'
15
+ s.homepage = 'https://github.com/dmagliola/loop_hard'
16
+ s.license = 'MIT'
17
+
18
+ s.files = `git ls-files`.split($/)
19
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ s.test_files = s.files.grep(%r{^(test|s.features)/})
21
+ s.require_paths = ["lib"]
22
+
23
+ s.required_ruby_version = ">= 1.9.3"
24
+
25
+ s.add_runtime_dependency "logger"
26
+
27
+ s.add_development_dependency "bundler"
28
+ s.add_development_dependency "rake"
29
+
30
+ s.add_development_dependency "minitest"
31
+ s.add_development_dependency "minitest-reporters"
32
+ s.add_development_dependency "shoulda"
33
+ s.add_development_dependency "mocha"
34
+ s.add_development_dependency "simplecov"
35
+
36
+ s.add_development_dependency "sidekiq"
37
+
38
+ s.add_development_dependency "coveralls"
39
+ s.add_development_dependency "codeclimate-test-reporter"
40
+ end
@@ -0,0 +1,7 @@
1
+ require_relative "test_helper"
2
+
3
+ class LoopHardTest < MiniTest::Test
4
+ should "run tests" do
5
+ assert true
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "test_helper"
2
+
3
+ class LoopTest < MiniTest::Test
4
+ should "loop until break if nothing stops it" do
5
+ i = 0
6
+ LoopHard.loop do
7
+ i += 1
8
+ break if i == 10
9
+ end
10
+
11
+ assert_equal 10, i
12
+ end
13
+
14
+ should "loop for 100ms" do
15
+ i = 0
16
+ LoopHard.loop(timeout: 0.1) do
17
+ sleep 0.01
18
+ i += 1
19
+ end
20
+
21
+ # Not an exact science, this thing...
22
+ assert_operator i, :>, 7
23
+ assert_operator i, :<, 12
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require_relative "test_helper"
2
+
3
+ class SidekiqTrapTest < MiniTest::Test
4
+ should "continue if Sidekiq isn't there" do
5
+ assert LoopHard::SidekiqTrap.continue?
6
+ end
7
+
8
+ # This test is EXTREMELY important, because we're basically using undocumented shit in Sidekiq, so it needs to
9
+ # do this in the most abstracted way possible
10
+ # Unfortunately, I can't really do that, because to get Sidekiq to the point of listening to signals, I need to
11
+ # start too much crap, so I just need to hope that if this changes, the internals will change enough that this will
12
+ # raise somehow
13
+ should "sidekiq kill" do
14
+ require 'sidekiq'
15
+ require 'sidekiq/cli'
16
+ require 'celluloid/current'
17
+ require 'sidekiq/launcher'
18
+
19
+ assert LoopHard::SidekiqTrap.continue?
20
+ assert_equal true, !!Sidekiq.server?
21
+ assert_equal true, !!defined?(Sidekiq::Fetcher)
22
+ assert_equal false, !!Sidekiq::Fetcher.done?
23
+
24
+ Sidekiq::Fetcher.done! # This is one of the many things that end up happening when Sidekiq gets a USR1 or TERM signal
25
+
26
+ assert_equal false, LoopHard::SidekiqTrap.continue?
27
+
28
+ Sidekiq::Fetcher.reset # Reset it so the rest of Sidekiq still works
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "test_helper"
2
+
3
+ class SignalTrapTest < MiniTest::Test
4
+ should "continue if no signals have been trapped" do
5
+ assert LoopHard::SignalTrap.continue?
6
+ end
7
+
8
+ should "stop if a signal has been trapped" do
9
+ LoopHard::SignalTrap.signal_trapped("TERM")
10
+ assert_equal false, LoopHard::SignalTrap.continue?
11
+ LoopHard::SignalTrap.reset_signal_trapped
12
+ end
13
+
14
+ should "reset signal trap on demand" do
15
+ LoopHard::SignalTrap.signal_trapped("TERM")
16
+ assert_equal false, LoopHard::SignalTrap.continue?
17
+ LoopHard::SignalTrap.reset_signal_trapped
18
+ assert_equal true, LoopHard::SignalTrap.continue?
19
+ end
20
+
21
+ should "trap signals" do
22
+ LoopHard::SignalTrap.trap_signals
23
+ assert_nil LoopHard::SignalTrap.instance_variable_get(:@signal_trapped)
24
+ Process.kill "TERM", Process.pid
25
+ assert_equal "TERM", LoopHard::SignalTrap.instance_variable_get(:@signal_trapped)
26
+ LoopHard::SignalTrap.reset_signal_trapped
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ require "rubygems"
2
+
3
+ require "simplecov"
4
+ require "coveralls"
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ Coveralls::SimpleCov::Formatter
8
+ ]
9
+ SimpleCov.start do
10
+ add_filter "/test/"
11
+ add_filter "/gemfiles/vendor"
12
+ end
13
+
14
+ require "codeclimate-test-reporter"
15
+ CodeClimate::TestReporter.start
16
+
17
+ require "minitest/autorun"
18
+ require "minitest/reporters"
19
+ MiniTest::Reporters.use!
20
+
21
+ require "shoulda"
22
+ require "shoulda-context"
23
+ require "shoulda-matchers"
24
+ require "mocha/setup"
25
+
26
+ # Make the code to be tested easy to load.
27
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
28
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
29
+
30
+ require 'active_support/testing/assertions'
31
+ include ActiveSupport::Testing::Assertions
32
+
33
+ require "benchmark"
34
+
35
+ require "loop_hard"
36
+
37
+ LoopHard.logger = Logger.new("/dev/null")
@@ -0,0 +1,15 @@
1
+ require_relative "test_helper"
2
+
3
+ class TimeoutTrapTest < MiniTest::Test
4
+ should "continue if no options are passed in" do
5
+ assert LoopHard::TimeoutTrap.continue?({})
6
+ end
7
+
8
+ should "continue if the maximum end time is in the future" do
9
+ assert LoopHard::TimeoutTrap.continue?(maximum_end_time: Time.now + 2)
10
+ end
11
+
12
+ should "stop if the maximum end time is in the past" do
13
+ assert_equal false, LoopHard::TimeoutTrap.continue?(maximum_end_time: Time.now - 2)
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,228 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loop_hard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Magliola
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logger
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: rake
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: minitest
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: minitest-reporters
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: shoulda
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'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mocha
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sidekiq
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: coveralls
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: codeclimate-test-reporter
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: "LoopHard allows you to have long-running worker loops that will stop
168
+ after a while,\n and also stop if they get an external signal
169
+ to stop (for example, if Sidekiq stops due to a USR1,\n or a
170
+ KILL signal is trapped.\n "
171
+ email: dmagliola@crystalgears.com
172
+ executables: []
173
+ extensions: []
174
+ extra_rdoc_files: []
175
+ files:
176
+ - ".gitignore"
177
+ - ".travis.yml"
178
+ - Gemfile
179
+ - LICENSE
180
+ - README.md
181
+ - Rakefile
182
+ - lib/loop_hard.rb
183
+ - lib/loop_hard/logger.rb
184
+ - lib/loop_hard/loop.rb
185
+ - lib/loop_hard/sidekiq_trap.rb
186
+ - lib/loop_hard/signal_trap.rb
187
+ - lib/loop_hard/timeout_trap.rb
188
+ - lib/loop_hard/version.rb
189
+ - loop_hard.gemspec
190
+ - test/loop_hard_test.rb
191
+ - test/loop_test.rb
192
+ - test/sidekiq_trap_test.rb
193
+ - test/signal_trap_test.rb
194
+ - test/test_helper.rb
195
+ - test/timeout_trap_test.rb
196
+ homepage: https://github.com/dmagliola/loop_hard
197
+ licenses:
198
+ - MIT
199
+ metadata: {}
200
+ post_install_message:
201
+ rdoc_options: []
202
+ require_paths:
203
+ - lib
204
+ required_ruby_version: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: 1.9.3
209
+ required_rubygems_version: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ requirements: []
215
+ rubyforge_project:
216
+ rubygems_version: 2.4.3
217
+ signing_key:
218
+ specification_version: 4
219
+ summary: Have long-running loops with a timeout, that listen to signals to know if
220
+ they should stop prematurely
221
+ test_files:
222
+ - test/loop_hard_test.rb
223
+ - test/loop_test.rb
224
+ - test/sidekiq_trap_test.rb
225
+ - test/signal_trap_test.rb
226
+ - test/test_helper.rb
227
+ - test/timeout_trap_test.rb
228
+ has_rdoc: