perfectqueue 0.8.5 → 0.8.6

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.
data/ChangeLog CHANGED
@@ -1,4 +1,11 @@
1
1
 
2
+ == 2012-07-23 version 0.8.6
3
+
4
+ * rdb_backend supports 'max_running' which is added in v0.7.21
5
+ * Fixed DaemonsLogger to use File::CREAT flag to open a file
6
+ * Show version when worker starts
7
+
8
+
2
9
  == 2012-07-06 version 0.8.5
3
10
 
4
11
  * Introduced PerfectQueue::Application::Decider
@@ -34,6 +41,16 @@ in case it was called within 1 second
34
41
  * Redesigned API
35
42
 
36
43
 
44
+ == 2012-07-19 version 0.7.22
45
+
46
+ * Fixed RDBBackend SQL
47
+
48
+
49
+ == 2012-07-19 version 0.7.21
50
+
51
+ * Added max_running column for fair resource restriction
52
+
53
+
37
54
  == 2012-01-24 version 0.7.20
38
55
 
39
56
  * Fixed MonitorThread
@@ -43,8 +43,8 @@ module PerfectQueue
43
43
  # connection test
44
44
  }
45
45
 
46
- @sql = <<SQL
47
- SELECT id, timeout, data, created_at, resource
46
+ @sql = <<SQL
47
+ SELECT id, timeout, data, created_at, resource, max_running-running AS runnable
48
48
  FROM `#{@table}`
49
49
  LEFT JOIN (
50
50
  SELECT resource AS res, COUNT(1) AS running
@@ -52,8 +52,9 @@ LEFT JOIN (
52
52
  WHERE timeout > ? AND created_at IS NOT NULL AND resource IS NOT NULL
53
53
  GROUP BY resource
54
54
  ) AS R ON resource = res
55
- WHERE timeout <= ? AND (running IS NULL OR running < #{MAX_RESOURCE})
56
- ORDER BY timeout ASC LIMIT #{MAX_SELECT_ROW}
55
+ WHERE timeout <= ? AND (max_running-running IS NULL OR max_running-running > 0)
56
+ ORDER BY runnable IS NOT NULL, runnable DESC, timeout ASC
57
+ LIMIT #{MAX_SELECT_ROW}
57
58
  SQL
58
59
 
59
60
  # sqlite doesn't support SELECT ... FOR UPDATE but
@@ -78,6 +79,7 @@ SQL
78
79
  data BLOB NOT NULL,
79
80
  created_at INT,
80
81
  resource VARCHAR(256),
82
+ max_running INT,
81
83
  PRIMARY KEY (id)
82
84
  );]
83
85
  connect {
@@ -122,14 +124,17 @@ SQL
122
124
  def submit(key, type, data, options)
123
125
  now = (options[:now] || Time.now).to_i
124
126
  run_at = (options[:run_at] || now).to_i
125
- user = options[:user]
126
- priority = options[:priority] # not supported
127
+ user = options[:user].to_s
128
+ max_running = options[:max_running]
127
129
  data = data ? data.dup : {}
128
130
  data['type'] = type
129
131
 
130
132
  connect {
131
133
  begin
132
- n = @db["INSERT INTO `#{@table}` (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?);", key, run_at, data.to_json, now, user].insert
134
+ n = @db[
135
+ "INSERT INTO `#{@table}` (id, timeout, data, created_at, resource, max_running) VALUES (?, ?, ?, ?, ?, ?);",
136
+ key, run_at, data.to_json, now, user, max_running
137
+ ].insert
133
138
  return Task.new(@client, key)
134
139
  rescue Sequel::DatabaseError
135
140
  raise IdempotentAlreadyExistsError, "task key=#{key} already exists"
@@ -26,7 +26,7 @@ module PerfectQueue
26
26
  @stderr_hook = false
27
27
  if dev.is_a?(String)
28
28
  @path = dev
29
- @io = File.open(dev, File::WRONLY|File::APPEND)
29
+ @io = File.open(@path, File::WRONLY|File::APPEND|File::CREAT)
30
30
  else
31
31
  @io = dev
32
32
  end
@@ -95,7 +95,7 @@ module PerfectQueue
95
95
 
96
96
  private
97
97
  def enqueue(sig)
98
- if Thread.current == self
98
+ if Thread.current == @thread
99
99
  @queue << sig
100
100
  if @mutex.locked?
101
101
  @cond.signal
@@ -78,6 +78,8 @@ module PerfectQueue
78
78
  end
79
79
 
80
80
  class TaskMetadata
81
+ include Model
82
+
81
83
  def initialize(client, key, attributes)
82
84
  super(client)
83
85
  @key = key
@@ -1,3 +1,3 @@
1
1
  module PerfectQueue
2
- VERSION = "0.8.5"
2
+ VERSION = "0.8.6"
3
3
  end
@@ -31,21 +31,20 @@ module PerfectQueue
31
31
  @runner = runner
32
32
  block = Proc.new { config } if config
33
33
  @config_load_proc = block
34
- @finished = false
35
34
  end
36
35
 
37
36
  def run
38
- @sig = install_signal_handlers
39
- begin
37
+ @log.info "PerfectQueue #{VERSION}"
38
+
39
+ install_signal_handlers do
40
40
  @engine = Engine.new(@runner, load_config)
41
41
  begin
42
42
  @engine.run
43
43
  ensure
44
44
  @engine.shutdown(true)
45
45
  end
46
- ensure
47
- @sig.shutdown
48
46
  end
47
+
49
48
  return nil
50
49
  rescue
51
50
  @log.error "#{$!.class}: #{$!}"
@@ -112,8 +111,8 @@ module PerfectQueue
112
111
  return config
113
112
  end
114
113
 
115
- def install_signal_handlers
116
- SignalQueue.start do |sig|
114
+ def install_signal_handlers(&block)
115
+ sig = SignalQueue.start do |sig|
117
116
  sig.trap :TERM do
118
117
  stop(false)
119
118
  end
@@ -147,6 +146,12 @@ module PerfectQueue
147
146
 
148
147
  trap :CHLD, "SIG_IGN"
149
148
  end
149
+
150
+ begin
151
+ block.call
152
+ ensure
153
+ sig.shutdown
154
+ end
150
155
  end
151
156
  end
152
157
 
data/spec/queue_spec.rb CHANGED
@@ -1,13 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Queue do
4
- let :queue do
5
- create_test_queue
6
- end
7
-
8
- after do
9
- queue.client.close
10
- end
4
+ include QueueTest
11
5
 
12
6
  it 'is a Queue' do
13
7
  queue.class.should == PerfectQueue::Queue
@@ -36,5 +36,35 @@ describe Backend::RDBCompatBackend do
36
36
  t.type.should == 'query'
37
37
  t.key.should == 'query.379474'
38
38
  end
39
+
40
+ it 'resource limit' do
41
+ time = Time.now.to_i
42
+
43
+ 3.times do |i|
44
+ queue.submit("test_#{i}", 'user01', {}, :now=>time-(i+1), :user=>'u1', :max_running=>2)
45
+ end
46
+ queue.submit("test_5", 'user02', {}, :now=>time, :user=>'u2', :max_running=>2)
47
+
48
+ task1 = queue.poll(:now=>time+10)
49
+ task1.should_not == nil
50
+ task1.type.should == 'user01'
51
+
52
+ task2 = queue.poll(:now=>time+10)
53
+ task2.should_not == nil
54
+ task2.type.should == 'user02'
55
+
56
+ task3 = queue.poll(:now=>time+10)
57
+ task3.should_not == nil
58
+ task3.type.should == 'user01'
59
+
60
+ task4 = queue.poll(:now=>time+10)
61
+ task4.should == nil
62
+
63
+ task1.finish!
64
+
65
+ task5 = queue.poll(:now=>time+10)
66
+ task5.should_not == nil
67
+ task5.type.should == 'user01'
68
+ end
39
69
  end
40
70
 
data/spec/spec_helper.rb CHANGED
@@ -13,32 +13,38 @@ end
13
13
 
14
14
  require 'fileutils'
15
15
 
16
- def test_queue_config
17
- {:type=>'rdb_compat', :url=>'sqlite://spec/test.db', :table=>'test_tasks', :processor_type=>'thread'}
18
- end
19
-
20
- def create_test_queue
21
- FileUtils.rm_f 'spec/test.db'
22
- queue = PerfectQueue.open(test_queue_config)
23
-
24
- sql = %[
25
- CREATE TABLE IF NOT EXISTS `test_tasks` (
26
- id VARCHAR(256) NOT NULL,
27
- timeout INT NOT NULL,
28
- data BLOB NOT NULL,
29
- created_at INT,
30
- resource VARCHAR(256),
31
- PRIMARY KEY (id)
32
- );]
33
-
34
- queue.client.backend.db.run sql
35
-
36
- return queue
16
+ module QueueTest
17
+ def self.included(mod)
18
+ mod.module_eval do
19
+ let :database_path do
20
+ 'spec/test.db'
21
+ end
22
+
23
+ let :queue_config do
24
+ {
25
+ :type => 'rdb_compat',
26
+ :url => "sqlite://#{database_path}",
27
+ :table => 'test_tasks',
28
+ :processor_type => 'thread'
29
+ }
30
+ end
31
+
32
+ let :queue do
33
+ PerfectQueue.open(queue_config)
34
+ end
35
+
36
+ before do
37
+ FileUtils.rm_f database_path
38
+ queue.client.init_database
39
+ end
40
+
41
+ after do
42
+ queue.close
43
+ end
44
+ end
45
+ end
37
46
  end
38
47
 
39
- def get_test_queue
40
- PerfectQueue.open(test_queue_config)
41
- end
42
48
 
43
49
  include PerfectQueue
44
50
 
data/spec/stress.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Queue do
4
+ include QueueTest
5
+
6
+ let :thread_num do
7
+ 5
8
+ end
9
+
10
+ let :loop_num do
11
+ 50
12
+ end
13
+
14
+ let :now do
15
+ Time.now.to_i
16
+ end
17
+
18
+ def thread_main
19
+ thread_id = Thread.current.object_id
20
+
21
+ loop_num.times do |i|
22
+ queue.submit("#{thread_id}-#{i}", "type01", {}, :now=>now-10)
23
+ task = queue.poll(:now=>now, :alive_time=>60)
24
+ task.should_not == nil
25
+ task.heartbeat!(:now=>now, :alive_time=>70)
26
+ task.finish!(:now=>now, :retention_time=>80)
27
+ end
28
+ end
29
+
30
+ it 'stress' do
31
+ puts "stress test with threads=#{thread_num} * loop_num=#{loop_num} = #{thread_num * loop_num} tasks"
32
+
33
+ # initialize queue here
34
+ queue
35
+ now
36
+
37
+ start_at = Time.now
38
+ (1..thread_num).map {
39
+ Thread.new(&method(:thread_main))
40
+ }.each {|thread|
41
+ thread.join
42
+ }
43
+ finish_at = Time.now
44
+
45
+ elapsed = finish_at - start_at
46
+ task_num = thread_num * loop_num
47
+ puts "#{elapsed} sec."
48
+ puts "#{task_num / elapsed} req/sec."
49
+ puts "#{elapsed / task_num} sec/req."
50
+ end
51
+
52
+ end
53
+
data/spec/worker_spec.rb CHANGED
@@ -29,9 +29,10 @@ class TestApp < PerfectQueue::Application::Dispatch
29
29
  end
30
30
 
31
31
  describe Worker do
32
+ include QueueTest
33
+
32
34
  before do
33
- create_test_queue.close
34
- @worker = Worker.new(TestApp, test_queue_config)
35
+ @worker = Worker.new(TestApp, queue_config)
35
36
  @thread = Thread.new {
36
37
  @worker.run
37
38
  }
@@ -42,17 +43,11 @@ describe Worker do
42
43
  @thread.join
43
44
  end
44
45
 
45
- def submit(*args)
46
- queue = get_test_queue
47
- queue.submit(*args)
48
- queue.close
49
- end
50
-
51
46
  it 'route' do
52
47
  TestHandler.any_instance.should_receive(:run).once
53
48
  RegexpHandler.any_instance.should_receive(:run).once
54
- submit('task01', 'test', {})
55
- submit('task02', 'reg01', {})
49
+ queue.submit('task01', 'test', {})
50
+ queue.submit('task02', 'reg01', {})
56
51
  sleep 1
57
52
  end
58
53
 
@@ -72,7 +67,7 @@ describe Worker do
72
67
 
73
68
  it 'kill reason' do
74
69
  TestHandler.any_instance.should_receive(:kill).once #.with(kind_of(PerfectQueue::CancelRequestedError)) # FIXME 'with' dead locks
75
- submit('task01', 'test', {'sleep'=>4})
70
+ queue.submit('task01', 'test', {'sleep'=>4})
76
71
  sleep 2
77
72
  Process.kill(:TERM, Process.pid)
78
73
  @thread.join
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfectqueue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.8.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-07 00:00:00.000000000Z
12
+ date: 2012-07-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sequel
16
- requirement: &70242051861300 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 3.26.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70242051861300
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.26.0
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rake
27
- requirement: &70242051860640 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 0.9.2
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70242051860640
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.2
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rspec
38
- requirement: &70242051859840 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: 2.10.0
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70242051859840
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.10.0
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: simplecov
49
- requirement: &70242051858860 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ~>
@@ -54,10 +69,15 @@ dependencies:
54
69
  version: 0.5.4
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *70242051858860
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.5.4
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: sqlite3
60
- requirement: &70242051858120 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ~>
@@ -65,7 +85,12 @@ dependencies:
65
85
  version: 1.3.3
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *70242051858120
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.3.3
69
94
  description: Highly available distributed cron built on RDBMS
70
95
  email: frsyuki@gmail.com
71
96
  executables:
@@ -112,6 +137,7 @@ files:
112
137
  - spec/queue_spec.rb
113
138
  - spec/rdb_compat_backend_spec.rb
114
139
  - spec/spec_helper.rb
140
+ - spec/stress.rb
115
141
  - spec/worker_spec.rb
116
142
  homepage: https://github.com/treasure-data/perfectqueue
117
143
  licenses: []
@@ -133,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
159
  version: '0'
134
160
  requirements: []
135
161
  rubyforge_project:
136
- rubygems_version: 1.8.12
162
+ rubygems_version: 1.8.23
137
163
  signing_key:
138
164
  specification_version: 3
139
165
  summary: Highly available distributed cron built on RDBMS
@@ -141,5 +167,6 @@ test_files:
141
167
  - spec/queue_spec.rb
142
168
  - spec/rdb_compat_backend_spec.rb
143
169
  - spec/spec_helper.rb
170
+ - spec/stress.rb
144
171
  - spec/worker_spec.rb
145
172
  has_rdoc: false