creeper 0.0.1

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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in creeper.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 lyon
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Creeper
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'creeper'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install creeper
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new('spec')
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/creeper/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["lyon"]
6
+ gem.email = ["lyondhill@gmail.com"]
7
+ gem.description = %q{Stalker with threads}
8
+ gem.summary = %q{A better solution for io bound jobs, same as stalker in functionality but more threadie.}
9
+ gem.homepage = "https://github.com/lyondhill/creeper"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "creeper"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Creeper::VERSION
17
+
18
+ gem.add_dependency "beanstalk-client"
19
+ gem.add_development_dependency 'rspec'
20
+ gem.add_dependency 'pry'
21
+
22
+ end
23
+
@@ -0,0 +1,161 @@
1
+ require 'beanstalk-client'
2
+ require "creeper/version"
3
+ require "creeper/worker"
4
+ require 'json'
5
+ require 'uri'
6
+ require 'timeout'
7
+
8
+ STDOUT.sync = true
9
+
10
+ module Creeper
11
+ extend self
12
+
13
+ def connect(url)
14
+ @@url = url
15
+ beanstalk
16
+ end
17
+
18
+ def enqueue(job, args={}, opts={})
19
+ pri = opts[:pri] || 65536
20
+ delay = [0, opts[:delay].to_i].max
21
+ ttr = opts[:ttr] || 120
22
+ beanstalk.use job
23
+ beanstalk.put [ job, args ].to_json, pri, delay, ttr
24
+ rescue Beanstalk::NotConnected => e
25
+ failed_connection(e)
26
+ end
27
+
28
+ def job(j, &block)
29
+ @@handlers ||= {}
30
+ @@handlers[j] = block
31
+ end
32
+
33
+ def before(&block)
34
+ @@before_handlers ||= []
35
+ @@before_handlers << block
36
+ end
37
+
38
+ def error(&blk)
39
+ @@error_handler = blk
40
+ end
41
+
42
+ def running
43
+ @@running ||= []
44
+ end
45
+
46
+ def soft_quit?
47
+ @@soft_quit ||= false
48
+ end
49
+
50
+ def soft_quit=(soft_quit)
51
+ @@soft_quit = soft_quit
52
+ end
53
+
54
+ def work(jobs=nil, thread_count=1)
55
+ thread_count.times do
56
+ w = Creeper::Worker.new()
57
+ t = Thread.new do
58
+ w.work(jobs)
59
+ end
60
+ running << {thread: t, worker: w}
61
+ end
62
+
63
+ while not soft_quit?
64
+ running.each_with_index do |runner, index|
65
+ if not runner[:thread].alive?
66
+ w = Creeper::Worker.new()
67
+ t = Thread.new do
68
+ w.work(jobs)
69
+ end
70
+ running[index] << {thread: t, worker: w}
71
+ end
72
+ end
73
+ sleep 1
74
+ end
75
+ running.each do |runner|
76
+ if runner[:worker].job_in_progress?
77
+ log "Murder [scheduling]"
78
+ runner[:worker].soft_quit = true
79
+ else
80
+ log "Murder [now]"
81
+ runner[:thread].kill
82
+ end
83
+ end
84
+ running.each do |runner|
85
+ runner[:thread].join
86
+ end
87
+ log "SEPPUKU!!"
88
+ end
89
+
90
+ def failed_connection(e)
91
+ log_error exception_message(e)
92
+ log_error "*** Failed connection to #{beanstalk_url}"
93
+ log_error "*** Check that beanstalkd is running (or set a different BEANSTALK_URL)"
94
+ exit 1
95
+ end
96
+
97
+ def log(msg)
98
+ puts msg
99
+ end
100
+
101
+ def log_error(msg)
102
+ STDERR.puts msg
103
+ end
104
+
105
+ def beanstalk
106
+ @@beanstalk ||= Beanstalk::Pool.new(beanstalk_addresses)
107
+ end
108
+
109
+ def beanstalk_url
110
+ return @@url if defined?(@@url) and @@url
111
+ ENV['BEANSTALK_URL'] || 'beanstalk://localhost/'
112
+ end
113
+
114
+ class BadURL < RuntimeError; end
115
+
116
+ def beanstalk_addresses
117
+ uris = beanstalk_url.split(/[\s,]+/)
118
+ uris.map {|uri| beanstalk_host_and_port(uri)}
119
+ end
120
+
121
+ def beanstalk_host_and_port(uri_string)
122
+ uri = URI.parse(uri_string)
123
+ raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
124
+ "#{uri.host}:#{uri.port || 11300}"
125
+ end
126
+
127
+ def exception_message(e)
128
+ msg = [ "Exception #{e.class} -> #{e.message}" ]
129
+
130
+ base = File.expand_path(Dir.pwd) + '/'
131
+ e.backtrace.each do |t|
132
+ msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
133
+ end
134
+
135
+ msg.join("\n")
136
+ end
137
+
138
+ def all_jobs
139
+ @@handlers.keys
140
+ end
141
+
142
+ def job_handlers
143
+ @@handlers ||= {}
144
+ end
145
+
146
+ def before_handlers
147
+ @@before_handlers ||= []
148
+ end
149
+
150
+ def error_handler
151
+ @@error_handler ||= nil
152
+ end
153
+
154
+ def clear!
155
+ @@soft_quit = false
156
+ @@running = []
157
+ @@handlers = nil
158
+ @@before_handlers = nil
159
+ @@error_handler = nil
160
+ end
161
+ end
@@ -0,0 +1,3 @@
1
+ module Creeper
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,168 @@
1
+ require 'beanstalk-client'
2
+ require 'json'
3
+ require 'uri'
4
+ require 'timeout'
5
+
6
+ class Creeper::Worker
7
+ def initialize
8
+ @handlers = Creeper.job_handlers
9
+ @before_handlers = Creeper.before_handlers
10
+ @error_handler = Creeper.error_handler
11
+ end
12
+
13
+ class NoJobsDefined < RuntimeError; end
14
+ class NoSuchJob < RuntimeError; end
15
+ class JobTimeout < RuntimeError; end
16
+ class BadURL < RuntimeError; end
17
+
18
+ def soft_quit?
19
+ @soft_quit ||= false
20
+ end
21
+
22
+ def soft_quit=(soft_quit)
23
+ # exit if !job_in_progress? && soft_quit
24
+ @soft_quit = soft_quit
25
+ end
26
+
27
+ def job_in_progress?
28
+ @in_progress ||= false
29
+ end
30
+
31
+ def prep(jobs=nil)
32
+ raise NoJobsDefined unless defined?(@handlers)
33
+ @error_handler = nil unless defined?(@error_handler)
34
+
35
+ jobs ||= all_jobs
36
+
37
+ jobs.each do |job|
38
+ raise(NoSuchJob, job) unless @handlers[job]
39
+ end
40
+
41
+ log "Working #{jobs.size} jobs: [ #{jobs.join(' ')} ]"
42
+
43
+ jobs.each { |job| beanstalk.watch(job) }
44
+
45
+ beanstalk.list_tubes_watched.each do |server, tubes|
46
+ tubes.each { |tube| beanstalk.ignore(tube) unless jobs.include?(tube) }
47
+ end
48
+ rescue Beanstalk::NotConnected => e
49
+ failed_connection(e)
50
+ end
51
+
52
+ def work(jobs=nil)
53
+ prep(jobs)
54
+ loop { work_one_job }
55
+ end
56
+
57
+ def work_one_job
58
+ Thread.current.kill if soft_quit?
59
+ job = beanstalk.reserve
60
+ name, args = JSON.parse job.body
61
+ log_job_begin(name, args)
62
+ handler = @handlers[name]
63
+ raise(NoSuchJob, name) unless handler
64
+
65
+ begin
66
+ if defined? @before_handlers and @before_handlers.respond_to? :each
67
+ @before_handlers.each do |block|
68
+ block.call(name)
69
+ end
70
+ end
71
+ handler.call(args)
72
+ end
73
+
74
+ job.delete
75
+ log_job_end(name)
76
+ rescue Beanstalk::NotConnected => e
77
+ failed_connection(e)
78
+ rescue SystemExit
79
+ raise
80
+ rescue => e
81
+ log_error exception_message(e)
82
+ job.bury rescue nil
83
+ log_job_end(name, 'failed') if @job_begun
84
+ if error_handler
85
+ if error_handler.arity == 1
86
+ error_handler.call(e)
87
+ else
88
+ error_handler.call(e, name, args)
89
+ end
90
+ end
91
+ end
92
+
93
+ def failed_connection(e)
94
+ log_error exception_message(e)
95
+ log_error "*** Failed connection to #{beanstalk_url}"
96
+ log_error "*** Check that beanstalkd is running (or set a different BEANSTALK_URL)"
97
+ exit 1
98
+ end
99
+
100
+ def log_job_begin(name, args)
101
+ @in_progress = true
102
+ args_flat = unless args.empty?
103
+ '(' + args.inject([]) do |accum, (key,value)|
104
+ accum << "#{key}=#{value}"
105
+ end.join(' ') + ')'
106
+ else
107
+ ''
108
+ end
109
+
110
+ log [ "Working", name, args_flat ].join(' ')
111
+ @job_begun = Time.now
112
+ end
113
+
114
+ def log_job_end(name, failed=false)
115
+ @in_progress = false
116
+ ellapsed = Time.now - @job_begun
117
+ ms = (ellapsed.to_f * 1000).to_i
118
+ log "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}"
119
+ end
120
+
121
+ def log(msg)
122
+ puts msg
123
+ end
124
+
125
+ def log_error(msg)
126
+ STDERR.puts msg
127
+ end
128
+
129
+ def beanstalk
130
+ @beanstalk ||= Beanstalk::Pool.new(beanstalk_addresses)
131
+ end
132
+
133
+ def beanstalk_url
134
+ Creeper.beanstalk_url
135
+ end
136
+
137
+ def beanstalk_addresses
138
+ uris = beanstalk_url.split(/[\s,]+/)
139
+ uris.map {|uri| beanstalk_host_and_port(uri)}
140
+ end
141
+
142
+ def beanstalk_host_and_port(uri_string)
143
+ uri = URI.parse(uri_string)
144
+ raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
145
+ "#{uri.host}:#{uri.port || 11300}"
146
+ end
147
+
148
+ def exception_message(e)
149
+ msg = [ "Exception #{e.class} -> #{e.message}" ]
150
+
151
+ base = File.expand_path(Dir.pwd) + '/'
152
+ e.backtrace.each do |t|
153
+ msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
154
+ end
155
+
156
+ msg.join("\n")
157
+ end
158
+
159
+ def all_jobs
160
+ @handlers.keys
161
+ end
162
+
163
+ def error_handler
164
+ @error_handler
165
+ end
166
+
167
+ end
168
+
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: creeper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - lyon
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: beanstalk-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: pry
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Stalker with threads
63
+ email:
64
+ - lyondhill@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.md
73
+ - Rakefile
74
+ - creeper.gemspec
75
+ - lib/creeper.rb
76
+ - lib/creeper/version.rb
77
+ - lib/creeper/worker.rb
78
+ homepage: https://github.com/lyondhill/creeper
79
+ licenses: []
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 1.8.20
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: A better solution for io bound jobs, same as stalker in functionality but
102
+ more threadie.
103
+ test_files: []
104
+ has_rdoc: