creeper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: