jober 0.0.1

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: 658e5f757cdbd193692c730c37853625a6ece891
4
+ data.tar.gz: 949f6f7ee9ede001aa2ebb14693ee4e537b94737
5
+ SHA512:
6
+ metadata.gz: 4c5152af3de453ca2f1dd7d0be59f44c52bff556b23dee3204140f18708133223da49f744f2f83acde333ee113c18f99190c65649829a3deee00a5c624a865b6
7
+ data.tar.gz: 47fa1ca5099ad4294d673cd2ce54b7347432ca75a3ed5e15e9c968b9208e093b5250ebefd0f1b2a80ce949b89b0261094d06a27ad32a31bf51731539b7797472
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.log
16
+ *.swp
17
+ todo
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in Jober.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 'Konstantin Makarchev'
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,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
3
+ require "Jober"
4
+
5
+ task_name = ARGV[0]
6
+ recursive = true
7
+
8
+ while true
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
3
+ require "jober"
4
+
5
+ # TODO: require application environment
6
+ # TODO: pass application name
7
+ # TODO: pass logger_path
8
+
9
+ m = Jober::Manager.new "test"
10
+ m.run
@@ -0,0 +1,72 @@
1
+ require 'bundler/setup'
2
+ Bundler.require :default
3
+
4
+ class A < Jober::Task
5
+ every 10
6
+ def perform
7
+ 10.times do |i|
8
+ info "enqueue to b #{i}"
9
+ B.enqueue(i)
10
+ end
11
+ end
12
+ end
13
+
14
+ class B < Jober::Queue
15
+ every 10
16
+ def perform(x)
17
+ 10.times do |i|
18
+ info "enqueue to c #{x} #{i}"
19
+ C.enqueue(x, i)
20
+ end
21
+ end
22
+ end
23
+
24
+ class C < Jober::Queue
25
+ every 10
26
+ def perform(x, i)
27
+ 10.times do |j|
28
+ info "enqueue to d #{x} #{i} #{j}"
29
+ D.enqueue(x, i, j)
30
+ end
31
+ end
32
+ end
33
+
34
+ class D < Jober::QueueBatch
35
+ every 10
36
+ batch_size 200
37
+
38
+ def perform(*batch)
39
+ info "got batch: #{batch.inspect}"
40
+ end
41
+ end
42
+
43
+ class E < Jober::Task
44
+ 5.times { every 5 }
45
+
46
+ def perform
47
+ puts "start e :)"
48
+ sleep 3
49
+ end
50
+ end
51
+
52
+ class G < Jober::Task
53
+ every 10
54
+
55
+ def perform
56
+ "asdfsdf" + 1
57
+ end
58
+ end
59
+
60
+ class F < Jober::Task
61
+ every 5
62
+ def perform
63
+ sleep 100
64
+ end
65
+ end
66
+
67
+ if $0 == __FILE__
68
+ require 'irb'
69
+ require 'irb/completion'
70
+
71
+ IRB.start
72
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler/setup'
2
+ Bundler.require :default
3
+ require_relative 'classes'
4
+
5
+ man = Jober::Manager.new "test"
6
+ man.logger_path = File.expand_path(__dir__)
7
+ man.run
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jober/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jober"
8
+ spec.version = Jober::VERSION
9
+ spec.authors = ["'Konstantin Makarchev'"]
10
+ spec.email = ["'kostya27@gmail.com'"]
11
+ spec.summary = %q{Simple background jobs, queues.}
12
+ spec.description = %q{Simple background jobs, queues.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'redis'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,91 @@
1
+ require "Jober/version"
2
+
3
+ require 'redis'
4
+ require 'logger'
5
+
6
+ module Jober
7
+ autoload :Manager, 'Jober/manager'
8
+ autoload :AbstractTask, 'Jober/abstract_task'
9
+ autoload :Task, 'Jober/task'
10
+ autoload :Queue, 'Jober/queue'
11
+ autoload :QueueBatch, 'Jober/queue_batch'
12
+ autoload :UniqueQueue, 'Jober/unique_queue'
13
+ autoload :Logger, 'Jober/logger'
14
+ autoload :SharedObject, 'Jober/shared_object'
15
+
16
+ class << self
17
+ attr_accessor :redis_config
18
+ attr_reader :classes
19
+
20
+ def logger
21
+ ::Logger.new(STDOUT)
22
+ end
23
+
24
+ def redis
25
+ Thread.current[:__redis__] ||= Redis.new(redis_config || {})
26
+ end
27
+
28
+ def reset_redis
29
+ Thread.current[:__redis__] = nil
30
+ end
31
+
32
+ def add_class(klass)
33
+ unless klass.to_s.start_with?('Jober::')
34
+ @classes ||= []
35
+ @classes << klass
36
+ end
37
+ end
38
+
39
+ def after_fork(&block)
40
+ @after_fork = block
41
+ end
42
+
43
+ def call_after_fork
44
+ @after_fork.call if @after_fork
45
+ end
46
+
47
+ def dump(obj)
48
+ Marshal.dump(obj)
49
+ end
50
+
51
+ def load(obj)
52
+ Marshal.load(obj)
53
+ end
54
+
55
+ def exception(ex)
56
+ # redefine me
57
+ end
58
+
59
+ def underscore(str)
60
+ word = str.dup
61
+ word.gsub!('::', '/')
62
+ word.gsub!(/(?:([A-Za-z\d])|^)((?=a)b)(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
63
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
64
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
65
+ word.tr!("-", "_")
66
+ word.downcase!
67
+ word
68
+ end
69
+
70
+ def llens
71
+ h = {}
72
+ @classes.each do |klass|
73
+ next unless klass.ancestors.include?(Jober::Queue)
74
+ h[klass.short_name] = klass.len
75
+ end
76
+ h
77
+ end
78
+
79
+ def stats
80
+ h = {}
81
+ @classes.each do |klass|
82
+ start = Jober.redis.get("Jober:stats:#{klass.short_name}:start")
83
+ start = Time.at(start.to_i) if start
84
+ _end = Jober.redis.get("Jober:stats:#{klass.short_name}:end")
85
+ _end = Time.at(_end.to_i) if _end
86
+ h[klass.short_name] = {:start => start, :end => _end, :duration => (_end && start && _end >= start) ? (_end - start) : nil }
87
+ end
88
+ h
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,46 @@
1
+ class Jober::AbstractTask
2
+ include Jober::Logger
3
+
4
+ class << self
5
+ def every(interval, method = :perform)
6
+ workers << [interval, method]
7
+ end
8
+
9
+ def workers
10
+ @workers ||= []
11
+ end
12
+
13
+ attr_accessor :short_name
14
+ end
15
+
16
+ attr_accessor :stopped
17
+
18
+ def self.inherited(base)
19
+ Jober.add_class(base)
20
+ end
21
+
22
+ def initialize
23
+ @stopped = false
24
+ trap("QUIT") { @stopped = true }
25
+ trap("INT") { @stopped = true }
26
+ end
27
+
28
+ def execute(method = :perform)
29
+ info "=> starting"
30
+ @start_at = Time.now
31
+ write_timestamp(:start)
32
+ run(method)
33
+ write_timestamp(:end)
34
+ info "<= end of #{method} in #{Time.now - @start_at}"
35
+ self
36
+ end
37
+
38
+ private
39
+
40
+ def write_timestamp(type)
41
+ Jober.redis.set("Jober:stats:#{self.class.short_name}:#{type}", Time.now.to_i.to_s)
42
+ rescue Object => ex
43
+ error "#{ex.inspect} #{ex.backtrace}"
44
+ end
45
+
46
+ end
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+
3
+ module Jober::Logger
4
+ def logger
5
+ @logger ||= Jober.logger
6
+ end
7
+
8
+ def logger=(logger)
9
+ @logger = logger
10
+ end
11
+
12
+ Logger::Severity.constants.each do |level|
13
+ method_name = level.to_s.downcase
14
+ define_method method_name do |msg = nil, &block|
15
+ logger.send(method_name, msg, &block)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,136 @@
1
+ require 'thread'
2
+
3
+ class Jober::Manager
4
+ include Jober::Logger
5
+
6
+ attr_accessor :logger_path, :stopped
7
+
8
+ def initialize(name, allowed_classes = nil)
9
+ @name = name
10
+ @stopped = false
11
+ @mutex = Mutex.new
12
+ @pids = []
13
+ @allowed_classes = allowed_classes ? (Jober.classes & allowed_classes) : Jober.classes
14
+
15
+ $0 = "#{@name} manager"
16
+ if @logger_path
17
+ self.logger = ::Logger.new(File.join(@logger_path, "manager.log"))
18
+ end
19
+ info "starting manager #{@name}"
20
+ end
21
+
22
+ def run!
23
+ @allowed_classes.each do |klass|
24
+ workers = klass.workers
25
+ workers = [[5 * 60, :perform]] if workers.empty?
26
+
27
+ workers.each do |interval, method|
28
+ Thread.new { start_worker(klass, interval, method) }
29
+ end
30
+ end
31
+ end
32
+
33
+ def run
34
+ run!
35
+
36
+ trap("TERM") { stop }
37
+
38
+ loop do
39
+ sleep 1
40
+ break if @stopped
41
+ end
42
+ end
43
+
44
+ def stop!
45
+ @stopped = true
46
+ @pids.each { |pid| ::Process.kill("QUIT", pid) }
47
+ info "stopping manager..."
48
+ end
49
+
50
+ def stop(timeout = 2.5)
51
+ stop!
52
+ return if @pids.empty?
53
+
54
+ sum = 0
55
+ while true
56
+ sleep(0.1)
57
+ sum += 0.1
58
+ break if sum >= timeout
59
+ break if @pids.empty?
60
+ end
61
+
62
+ return if @pids.empty?
63
+
64
+ info { "still alive pids: #{@pids}, killing" }
65
+ @pids.each { |pid| ::Process.kill("KILL", pid) }
66
+
67
+ @pids = []
68
+ end
69
+
70
+ def catch
71
+ yield
72
+ true
73
+ rescue Object => ex
74
+ p "exception #{ex.inspect} #{ex.backtrace}"
75
+ Jober.exception(ex)
76
+ nil
77
+ end
78
+
79
+ def start_worker(klass, interval, method)
80
+ debug { "start worker for #{klass.to_s} #{method}" }
81
+ loop do
82
+ pid = nil
83
+ res = catch do
84
+ pid = run_task_fork(klass, method)
85
+ add_pid(pid)
86
+ Process.wait(pid)
87
+ del_pid(pid)
88
+ sleep interval unless stopped
89
+ end
90
+ del_pid(pid)
91
+ break if stopped
92
+ sleep 0.5 unless res
93
+ break if stopped
94
+ end
95
+ end
96
+
97
+ def run_task_fork(klass, method)
98
+ info "invoke #{klass} #{method}"
99
+ fork do
100
+ $0 = "#{@name} manager #{klass}"
101
+ #$0 += " #{index}" if index > 0
102
+ Jober.call_after_fork
103
+ Jober.reset_redis
104
+
105
+ inst = klass.new # class_name parent of Jober::Task
106
+
107
+ if @logger_path
108
+ logger_path = File.join(@logger_path, "#{klass.short_name}.log")
109
+
110
+ STDOUT.reopen(File.open(logger_path, 'a'))
111
+ STDERR.reopen(File.open(logger_path, 'a'))
112
+ inst.logger = ::Logger.new(logger_path)
113
+ end
114
+
115
+ inst.execute(method)
116
+ end
117
+ end
118
+
119
+ def pids
120
+ @mutex.synchronize { @pids }
121
+ end
122
+
123
+ private
124
+
125
+ def add_pid(pid)
126
+ @mutex.synchronize { @pids << pid }
127
+ end
128
+
129
+ def del_pid(pid)
130
+ @mutex.synchronize { @pids -= [pid] }
131
+ end
132
+
133
+ def clear_pids
134
+ @mutex.synchronize { @pids = [] }
135
+ end
136
+ end
@@ -0,0 +1,47 @@
1
+ class Jober::Queue < Jober::Task
2
+
3
+ def self.inherited(base)
4
+ super
5
+ base.set_queue_name(base.short_name)
6
+ end
7
+
8
+ class << self
9
+ attr_accessor :queue_name
10
+
11
+ def set_queue_name(q)
12
+ @queue_name = "Jober:queue:#{q}"
13
+ end
14
+ end
15
+
16
+ def queue_name
17
+ self.class.queue_name
18
+ end
19
+
20
+ def self.enqueue(*args)
21
+ Jober.redis.rpush(queue_name, Jober.dump(args))
22
+ end
23
+
24
+ def self.len
25
+ Jober.redis.llen(self.queue_name)
26
+ end
27
+
28
+ def pop
29
+ res = Jober.redis.lpop(queue_name)
30
+ Jober.load(res) if res
31
+ end
32
+
33
+ def run(method)
34
+ cnt = 0
35
+ while args = pop
36
+ send(method, *args)
37
+ cnt += 1
38
+
39
+ if stopped
40
+ break
41
+ end
42
+
43
+ info { "processed #{cnt}" } if cnt % 1000 == 0
44
+ end
45
+ info { "processed #{cnt}" }
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ class Jober::QueueBatch < Jober::Queue
2
+ class << self
3
+ def batch_size(bs)
4
+ @batch_size = bs
5
+ end
6
+
7
+ def get_batch_size
8
+ @batch_size ||= 100
9
+ end
10
+ end
11
+
12
+ def execute(method = :perform)
13
+ cnt = 0
14
+ batch = []
15
+ while args = pop
16
+ batch << args
17
+ if batch.length >= self.class.get_batch_size
18
+ execute_batch(method, batch)
19
+ batch = []
20
+ end
21
+ break if stopped
22
+ cnt += 1
23
+ end
24
+ if batch.length > 0
25
+ if stopped
26
+ reschedule_batch(batch)
27
+ else
28
+ execute_batch(method, batch)
29
+ end
30
+ end
31
+ self
32
+ end
33
+
34
+ def execute_batch(method, batch)
35
+ send(method, batch)
36
+ rescue Object => ex
37
+ reschedule_batch(batch)
38
+ Jober.exception(ex)
39
+ end
40
+
41
+ private
42
+
43
+ def reschedule_batch(batch)
44
+ batch.reverse_each { |ev| Jober.redis.lpush(queue_name, Jober.dump(ev)) }
45
+ end
46
+
47
+ end
@@ -0,0 +1,48 @@
1
+ class Jober::SharedObject
2
+ class << self
3
+ def set(name, obj, conn = Jober.redis)
4
+ conn.set(key(name), Marshal.dump(obj))
5
+ end
6
+
7
+ def get(name, conn = Jober.redis)
8
+ r = conn.get(key(name))
9
+ Marshal.load(r) if r
10
+ end
11
+
12
+ def raw_get(name)
13
+ Jober.redis.get(key(name))
14
+ end
15
+
16
+ def [](name)
17
+ get(name)
18
+ end
19
+
20
+ def []=(name, obj)
21
+ set name, obj
22
+ end
23
+
24
+ def inc(name, by = 1)
25
+ Jober.redis.incrby(key(name), by)
26
+ end
27
+
28
+ def del(name)
29
+ Jober.redis.del key(name)
30
+ end
31
+
32
+ def clear
33
+ Jober.redis.keys(key('*')).each { |k| del(k) }
34
+ end
35
+
36
+ def values(mask)
37
+ Jober.redis.keys(key(mask)).map { |key| get(key) }
38
+ end
39
+
40
+ def key(name)
41
+ if name.start_with?('shared:')
42
+ name
43
+ else
44
+ "shared:#{name}"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ class Jober::Task < Jober::AbstractTask
2
+
3
+ def self.extract_name(name)
4
+ Jober.underscore(name).gsub(/_?queue_?/, '').gsub(/_?fk_?/, '').gsub(/_?task_?/, '').split('::').last.gsub('/', '-')
5
+ end
6
+
7
+ def self.inherited(base)
8
+ super
9
+ base.short_name = extract_name(base.name)
10
+ end
11
+
12
+ def perform
13
+ raise "implement me"
14
+ end
15
+
16
+ def run(method)
17
+ send(method)
18
+ end
19
+
20
+ end
@@ -0,0 +1,16 @@
1
+ class Jober::UniqueQueue < Jober::Queue
2
+
3
+ def self.len
4
+ Jober.redis.scard(queue_name)
5
+ end
6
+
7
+ def self.enqueue(*args)
8
+ Jober.redis.sadd(queue_name, Jober.dump(args))
9
+ end
10
+
11
+ def pop
12
+ res = Jober.redis.spop(queue_name)
13
+ Jober.load(res) if res
14
+ end
15
+
16
+ end
@@ -0,0 +1,3 @@
1
+ module Jober
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "spec_helper"
2
+
3
+ class MyChain < Jober::Task
4
+ every 3
5
+
6
+ def perform
7
+ sleep 5
8
+ SO["chain"] += 1
9
+ end
10
+ end
11
+
12
+ describe "Queue" do
13
+ it "should work" do
14
+ SO["chain"] = 0
15
+ run_manager_for(7, [MyChain])
16
+ SO["chain"].should == 1
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "spec_helper"
2
+
3
+ class A < Jober::Task
4
+ every 3000
5
+
6
+ def perform
7
+ 10.times do |i|
8
+ B.enqueue(i)
9
+ C.enqueue(i)
10
+ end
11
+ end
12
+ end
13
+
14
+ class B < Jober::Queue
15
+ every 3
16
+
17
+ def perform(x)
18
+ SO["b"] += x
19
+ end
20
+ end
21
+
22
+ class C < Jober::QueueBatch
23
+ every 3
24
+
25
+ def perform(batch)
26
+ SO["c"] = batch.flatten
27
+ end
28
+ end
29
+
30
+ describe "integration" do
31
+ it "should work" do
32
+ SO["b"] = 0
33
+ run_manager_for(5, [A, B, C])
34
+ SO["b"].should == 45
35
+ SO["c"].should == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "spec_helper"
2
+
3
+ class LongTask1 < Jober::Task
4
+ def perform
5
+ sleep 100
6
+ end
7
+ end
8
+
9
+ class LongTask11 < Jober::Task
10
+ def perform
11
+ while true
12
+ sleep 1
13
+ break if stopped
14
+ end
15
+ SO["11"] = true
16
+ end
17
+ end
18
+
19
+ class LongTask2 < Jober::Queue
20
+ def perform
21
+ sleep 1
22
+ SO["a"] += 1
23
+ end
24
+ end
25
+
26
+ describe "should kill long tasks" do
27
+ it "kill" do
28
+ t = Time.now
29
+ SO["a"] = 0
30
+ 100.times { LongTask2.enqueue }
31
+
32
+ m = run_manager_for(3, [LongTask1, LongTask11, LongTask2])
33
+
34
+ SO["a"].should == 3
35
+ m.pids.should == []
36
+ (Time.now - t).should < (3.2 + 2.5)
37
+ SO["11"].should == true
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "spec_helper"
2
+
3
+ class Man1 < Jober::Task
4
+ every 2
5
+
6
+ def perform
7
+ sleep 3
8
+ end
9
+ end
10
+
11
+ class Man2 < Jober::Task
12
+ every 2
13
+
14
+ def perform
15
+ sleep 2
16
+ raise "jopa"
17
+ end
18
+ end
19
+
20
+ describe "manage pids" do
21
+ it "should work" do
22
+ run_manager_for(nil, [Man1, Man2]) do |m|
23
+ sleep 1
24
+ m.pids.size.should == 2
25
+
26
+ sleep 1.5
27
+ m.pids.size.should == 1
28
+
29
+ sleep 1
30
+ m.pids.size.should == 0
31
+
32
+ sleep 1
33
+ m.pids.size.should == 1
34
+
35
+ sleep 1.1
36
+ m.pids.size.should == 2
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "spec_helper"
2
+
3
+ class MyBatchQueue < Jober::QueueBatch
4
+ batch_size 6
5
+
6
+ attr_reader :res
7
+
8
+ def perform(batch)
9
+ @res ||= []
10
+ @res << batch
11
+ end
12
+ end
13
+
14
+ describe "QueueBatch" do
15
+ it "should set internals" do
16
+ w = MyBatchQueue.new
17
+ w.queue_name.should == 'Jober:queue:my_batch'
18
+ end
19
+
20
+ it "should execute" do
21
+ 10.times { |i| MyBatchQueue.enqueue(i) }
22
+ MyBatchQueue.new.execute.res.should == [[[0], [1], [2], [3], [4], [5]], [[6], [7], [8], [9]]]
23
+ end
24
+
25
+ it "should register class" do
26
+ Jober.classes.should include(MyBatchQueue)
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "spec_helper"
2
+
3
+ class FkMyParallel < Jober::Queue
4
+ 5.times { |i| every 3 }
5
+
6
+ def perform(arg)
7
+ SO["fork:#{$$}"] ||= 0
8
+ SO["fork:#{$$}"] += 1
9
+ SO.inc("count", arg)
10
+ end
11
+ end
12
+
13
+ describe "Queue" do
14
+ it "should work" do
15
+ 1000.times { |i| FkMyParallel.enqueue(i) }
16
+ run_manager_for(2, [FkMyParallel])
17
+ SO.raw_get("count").to_i.should == 499500
18
+
19
+ vals = SO.values("fork*")
20
+ vals.size.should == 5
21
+ vals.all? { |el| el > 20 }.should == true
22
+ vals.inject(0, :+).should == 1000
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "spec_helper"
2
+
3
+ class MyQueue < Jober::Queue
4
+ def initialize(*args)
5
+ super
6
+ @counter = 0
7
+ end
8
+
9
+ attr_reader :counter
10
+
11
+ def perform(x)
12
+ @counter += x
13
+ end
14
+ end
15
+
16
+ class Jasdfoadsfjaf < Jober::Queue
17
+ set_queue_name 'human_name'
18
+
19
+ def perform
20
+ end
21
+ end
22
+
23
+ describe "Queue" do
24
+ it "should set internals" do
25
+ w = MyQueue.new
26
+ w.queue_name.should == 'Jober:queue:my'
27
+ end
28
+
29
+ it "should execute" do
30
+ MyQueue.enqueue(1)
31
+ MyQueue.enqueue(2)
32
+ MyQueue.enqueue(3)
33
+ MyQueue.new.execute.counter.should == 6
34
+ end
35
+
36
+ it "should register class" do
37
+ Jober.classes.should include(MyQueue)
38
+ end
39
+
40
+ it "custom queue name" do
41
+ 10.times { Jasdfoadsfjaf.enqueue }
42
+ p Jober.llens
43
+ Jober.llens['human_name'].should == 10
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "SO" do
4
+ it "should work" do
5
+ SO["a"].should == nil
6
+ SO["a"] = 0
7
+ SO["a"].should == 0
8
+ SO["a"] += 1
9
+ SO["a"].should == 1
10
+
11
+ SO["b"].should == nil
12
+ SO["b"] = [1, 2, 3, :bla, {"a" => 2.5}]
13
+ SO["b"].should == [1, 2, 3, :bla, {"a" => 2.5}]
14
+
15
+ SO.del("a")
16
+ SO["a"].should == nil
17
+
18
+ SO.clear
19
+ SO["b"].should == nil
20
+
21
+ SO["c"] ||= 0
22
+ SO["c"] += 1
23
+ SO["c"].should == 1
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ require "bundler/setup"
2
+ Bundler.require :default
3
+
4
+ Jober::SharedObject
5
+ SO = Jober::SharedObject
6
+
7
+ RSpec.configure do |config|
8
+ config.expect_with(:rspec) { |c| c.syntax = :should }
9
+
10
+ config.before(:each) do
11
+ SO.clear
12
+ Jober.redis.keys("Jober*").each { |k| Jober.redis.del(k) }
13
+ end
14
+
15
+ config.after(:all) do
16
+ SO.clear
17
+ end
18
+ end
19
+
20
+ def run_manager_for(timeout, classes, &block)
21
+ m = Jober::Manager.new "test", classes
22
+ m.run!
23
+
24
+ if block
25
+ block.call(m)
26
+ else
27
+ sleep timeout
28
+ end
29
+
30
+ m.stop
31
+ sleep 0.1
32
+ m
33
+ end
@@ -0,0 +1,33 @@
1
+ require_relative "spec_helper"
2
+
3
+ class MyQueue1 < Jober::Queue
4
+ def perform
5
+ sleep 2
6
+ end
7
+ end
8
+
9
+ class MyQueue2 < Jober::QueueBatch
10
+ def perform
11
+ end
12
+ end
13
+
14
+ describe "Stats" do
15
+ it "llens" do
16
+ 10.times { MyQueue1.enqueue }
17
+ 99.times { MyQueue2.enqueue }
18
+
19
+ h = Jober.llens
20
+ h['my1'].should == 10
21
+ h['my2'].should == 99
22
+ end
23
+
24
+ it "stats" do
25
+ MyQueue1.enqueue
26
+ run_manager_for(3, [MyQueue1, MyQueue2])
27
+ h = Jober.stats
28
+ h['my1'][:start].should be
29
+ h['my1'][:end].should be
30
+ h['my1'][:duration].should be_within(0.1).of(2.0)
31
+ h['my2'].should == {:start=>nil, :end=>nil, :duration=>nil}
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ require_relative "spec_helper"
2
+
3
+ class WorkerTask < Jober::Task
4
+ def initialize(*args)
5
+ super
6
+ @counter = 0
7
+ end
8
+
9
+ attr_reader :counter
10
+
11
+ def perform
12
+ 10.times { @counter += 1 }
13
+ end
14
+ end
15
+
16
+ class Task2 < Jober::Task
17
+ every 3, :bla
18
+ every 5
19
+
20
+ def bla
21
+ end
22
+
23
+ def perform
24
+ end
25
+ end
26
+
27
+ describe "Task" do
28
+ it "should execute" do
29
+ w = WorkerTask.new
30
+ w.execute.counter.should == 10
31
+ end
32
+
33
+ it "should set short_name" do
34
+ WorkerTask.short_name.should == 'worker'
35
+ end
36
+
37
+ it "should register class" do
38
+ Jober.classes.should include(WorkerTask)
39
+ end
40
+
41
+ it "set some schedulers" do
42
+ Task2.workers.should == [[3, :bla], [5, :perform]]
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ require_relative "spec_helper"
2
+
3
+ class MyUniqueQueue < Jober::UniqueQueue
4
+ def initialize(*args)
5
+ super
6
+ @counter = 0
7
+ end
8
+
9
+ attr_reader :counter
10
+
11
+ def perform(x)
12
+ @counter += x
13
+ end
14
+ end
15
+
16
+ describe "Queue" do
17
+ it "should set internals" do
18
+ w = MyUniqueQueue.new
19
+ w.queue_name.should == 'Jober:queue:my_unique'
20
+ end
21
+
22
+ it "should execute Only for unique values" do
23
+ MyUniqueQueue.enqueue(1)
24
+ MyUniqueQueue.enqueue(2)
25
+ MyUniqueQueue.enqueue(2)
26
+ MyUniqueQueue.enqueue(2)
27
+ MyUniqueQueue.enqueue(3)
28
+ MyUniqueQueue.enqueue(3)
29
+ MyUniqueQueue.enqueue(3)
30
+ MyUniqueQueue.len.should == 3
31
+ MyUniqueQueue.new.execute.counter.should == 6
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jober
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - "'Konstantin Makarchev'"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
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: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
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
+ description: Simple background jobs, queues.
70
+ email:
71
+ - "'kostya27@gmail.com'"
72
+ executables:
73
+ - jober
74
+ - manager
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - Rakefile
82
+ - bin/jober
83
+ - bin/manager
84
+ - examples/classes.rb
85
+ - examples/man.rb
86
+ - jober.gemspec
87
+ - lib/jober.rb
88
+ - lib/jober/abstract_task.rb
89
+ - lib/jober/logger.rb
90
+ - lib/jober/manager.rb
91
+ - lib/jober/queue.rb
92
+ - lib/jober/queue_batch.rb
93
+ - lib/jober/shared_object.rb
94
+ - lib/jober/task.rb
95
+ - lib/jober/unique_queue.rb
96
+ - lib/jober/version.rb
97
+ - spec/chain_spec.rb
98
+ - spec/integration_spec.rb
99
+ - spec/kill_task_spec.rb
100
+ - spec/pids_spec.rb
101
+ - spec/queue_batch_spec.rb
102
+ - spec/queue_parallel_spec.rb
103
+ - spec/queue_spec.rb
104
+ - spec/shared_object_spec.rb
105
+ - spec/spec_helper.rb
106
+ - spec/stats_spec.rb
107
+ - spec/task_spec.rb
108
+ - spec/unique_queue_spec.rb
109
+ homepage: ''
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.2.2
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Simple background jobs, queues.
133
+ test_files:
134
+ - spec/chain_spec.rb
135
+ - spec/integration_spec.rb
136
+ - spec/kill_task_spec.rb
137
+ - spec/pids_spec.rb
138
+ - spec/queue_batch_spec.rb
139
+ - spec/queue_parallel_spec.rb
140
+ - spec/queue_spec.rb
141
+ - spec/shared_object_spec.rb
142
+ - spec/spec_helper.rb
143
+ - spec/stats_spec.rb
144
+ - spec/task_spec.rb
145
+ - spec/unique_queue_spec.rb