perfectqueue 0.8.5 → 0.8.6

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