loop_hard 0.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.
@@ -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: