queue_classic 1.0.2 → 2.0.0rc1

Sign up to get free protection for your applications and to get access to all the features.
data/test/helper.rb CHANGED
@@ -1,26 +1,30 @@
1
1
  $: << File.expand_path("lib")
2
2
  $: << File.expand_path("test")
3
3
 
4
- ENV['DATABASE_URL'] ||= 'postgres:///queue_classic_test'
4
+ ENV["DATABASE_URL"] ||= "postgres:///queue_classic_test"
5
5
 
6
- require 'queue_classic'
7
- require 'database_helpers'
8
-
9
- require 'minitest/unit'
6
+ require "queue_classic"
7
+ require "minitest/unit"
10
8
  MiniTest::Unit.autorun
11
9
 
12
- def context(*args, &block)
13
- return super unless (name = args.first) && block
14
- klass = Class.new(MiniTest::Unit::TestCase) do
15
- def self.test(name, &block)
16
- define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
17
- end
18
- def self.xtest(*args) end
19
- def self.setup(&block) define_method(:setup, &block) end
20
- def self.teardown(&block) define_method(:teardown, &block) end
10
+ QC::Log.level = Logger::ERROR
11
+
12
+ class QCTest < MiniTest::Unit::TestCase
13
+
14
+ def setup
15
+ init_db
16
+ end
17
+
18
+ def teardown
19
+ QC.delete_all
20
+ end
21
+
22
+ def init_db(table_name="queue_classic_jobs")
23
+ QC::Conn.execute("SET client_min_messages TO 'warning'")
24
+ QC::Conn.execute("DROP TABLE IF EXISTS #{table_name} CASCADE")
25
+ QC::Conn.execute("CREATE TABLE #{table_name} (id serial, q_name varchar(255), method varchar(255), args text, locked_at timestamptz)")
26
+ QC::Queries.load_functions
27
+ QC::Conn.disconnect
21
28
  end
22
- (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
23
29
 
24
- klass.send :include, DatabaseHelpers
25
- klass.class_eval &block
26
30
  end
data/test/queue_test.rb CHANGED
@@ -1,78 +1,47 @@
1
1
  require File.expand_path("../helper.rb", __FILE__)
2
- require 'ostruct'
3
2
 
4
- context "Queue" do
3
+ class QueueTest < QCTest
5
4
 
6
- setup { @database = init_db }
7
-
8
- test "Queue class responds to enqueue" do
9
- QC::Queue.enqueue("Klass.method")
10
- end
11
-
12
- test "Queue class has a default table name" do
13
- default_table_name = QC::Database.new.table_name
14
- assert_equal default_table_name, QC::Queue.database.table_name
15
- end
16
-
17
- test "Queue class responds to dequeue" do
18
- QC::Queue.enqueue("Klass.method")
19
- assert_equal "Klass.method", QC::Queue.dequeue.signature
5
+ def test_enqueue
6
+ QC.enqueue("Klass.method")
20
7
  end
21
8
 
22
- test "Queue class responds to delete" do
23
- QC::Queue.enqueue("Klass.method")
24
- job = QC::Queue.dequeue
25
- QC::Queue.delete(job)
9
+ def test_lock
10
+ QC.enqueue("Klass.method")
11
+ expected = {:id=>"1", :method=>"Klass.method", :args=>[]}
12
+ assert_equal(expected, QC.lock)
26
13
  end
27
14
 
28
- test "Queue class responds to delete_all" do
29
- 2.times { QC::Queue.enqueue("Klass.method") }
30
- job1,job2 = QC::Queue.dequeue, QC::Queue.dequeue
31
- QC::Queue.delete_all
15
+ def test_lock_when_empty
16
+ assert_nil(QC.lock)
32
17
  end
33
18
 
34
- test "Queue class return the length of the queue" do
35
- 2.times { QC::Queue.enqueue("Klass.method") }
36
- assert_equal 2, QC::Queue.length
19
+ def test_count
20
+ QC.enqueue("Klass.method")
21
+ assert_equal(1, QC.count)
37
22
  end
38
23
 
39
- test "Queue class finds jobs using query method" do
40
- QC::Queue.enqueue("Something.hard_to_find")
41
- jobs = QC::Queue.query("Something.hard_to_find")
42
- assert_equal 1, jobs.length
43
- assert_equal "Something.hard_to_find", jobs.first.signature
24
+ def test_delete
25
+ QC.enqueue("Klass.method")
26
+ assert_equal(1, QC.count)
27
+ QC.delete(QC.lock[:id])
28
+ assert_equal(0, QC.count)
44
29
  end
45
30
 
46
- test "queue instance responds to enqueue" do
47
- QC::Queue.enqueue("Something.hard_to_find")
48
- tmp_db = init_db(:custom_queue_name)
49
- @queue = QC::Queue.new(:custom_queue_name)
50
- @queue.enqueue "Klass.method"
51
- @queue.database.disconnect
31
+ def test_delete_all
32
+ QC.enqueue("Klass.method")
33
+ QC.enqueue("Klass.method")
34
+ assert_equal(2, QC.count)
35
+ QC.delete_all
36
+ assert_equal(0, QC.count)
52
37
  end
53
38
 
54
- test "queue only uses 1 connection per class" do
55
- QC::Queue.length
56
- QC::Queue.enqueue "Klass.method"
57
- QC::Queue.delete QC::Queue.dequeue
58
- QC::Queue.enqueue "Klass.method"
59
- QC::Queue.dequeue
60
- assert_equal 1, @database.execute("SELECT count(*) from pg_stat_activity")[0]["count"].to_i
39
+ def test_queue_instance
40
+ queue = QC::Queue.new("queue_classic_jobs", false)
41
+ queue.enqueue("Klass.method")
42
+ assert_equal(1, queue.count)
43
+ queue.delete(queue.lock[:id])
44
+ assert_equal(0, queue.count)
61
45
  end
62
46
 
63
- test "Queue class enqueues a job" do
64
- job = OpenStruct.new :signature => 'Klass.method', :params => ['param']
65
- QC::Queue.enqueue(job)
66
- dequeued_job = QC::Queue.dequeue
67
- assert_equal "Klass.method", dequeued_job.signature
68
- assert_equal 'param', dequeued_job.params
69
- end
70
-
71
- test "Queues have their own array" do
72
- refute_equal(Class.new(QC::Queue).array, Class.new(QC::Queue).array)
73
- end
74
-
75
- test "Queues have their own database" do
76
- refute_equal(Class.new(QC::Queue).database, Class.new(QC::Queue).database)
77
- end
78
47
  end
data/test/worker_test.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require File.expand_path("../helper.rb", __FILE__)
2
2
 
3
- class TestNotifier
4
- def self.deliver(args={})
5
- end
3
+ module TestObject
4
+ extend self
5
+ def no_args; return nil; end
6
+ def one_arg(a); return a; end
7
+ def two_args(a,b); return [a,b]; end
6
8
  end
7
9
 
8
10
  # This not only allows me to test what happens
@@ -11,47 +13,77 @@ end
11
13
  # you want.
12
14
  class TestWorker < QC::Worker
13
15
  attr_accessor :failed_count
14
- def initialize
16
+
17
+ def initialize(*args)
18
+ super(*args)
15
19
  @failed_count = 0
16
- super
17
20
  end
21
+
18
22
  def handle_failure(job,e)
19
23
  @failed_count += 1
20
24
  end
21
25
  end
22
26
 
23
- context "Worker" do
27
+ class WorkerTest < QCTest
24
28
 
25
- setup do
26
- @database = init_db
27
- @worker = TestWorker.new
29
+ def test_work
30
+ QC.enqueue("TestObject.no_args")
31
+ worker = TestWorker.new("default", 1, false, false, 1)
32
+ assert_equal(1, QC.count)
33
+ worker.work
34
+ assert_equal(0, QC.count)
35
+ assert_equal(0, worker.failed_count)
28
36
  end
29
37
 
30
- teardown do
31
- @database.disconnect
38
+ def test_failed_job
39
+ QC.enqueue("TestObject.not_a_method")
40
+ worker = TestWorker.new("default", 1, false, false, 1)
41
+ worker.work
42
+ assert_equal(1, worker.failed_count)
32
43
  end
33
44
 
34
- test "working a job" do
35
- QC::Queue.enqueue "TestNotifier.deliver", {}
45
+ def test_work_with_no_args
46
+ QC.enqueue("TestObject.no_args")
47
+ worker = TestWorker.new("default", 1, false, false, 1)
48
+ r = worker.work
49
+ assert_nil(r)
50
+ assert_equal(0, worker.failed_count)
51
+ end
36
52
 
37
- assert_equal(1, QC::Queue.length)
38
- @worker.work
39
- assert_equal(0, QC::Queue.length)
40
- assert_equal(0, @worker.failed_count)
53
+ def test_work_with_one_arg
54
+ QC.enqueue("TestObject.one_arg", "1")
55
+ worker = TestWorker.new("default", 1, false, false, 1)
56
+ r = worker.work
57
+ assert_equal("1", r)
58
+ assert_equal(0, worker.failed_count)
41
59
  end
42
60
 
43
- test "resuce failed job" do
44
- QC::Queue.enqueue "TestNotifier.no_method", {}
61
+ def test_work_with_two_args
62
+ QC.enqueue("TestObject.two_args", "1", 2)
63
+ worker = TestWorker.new("default", 1, false, false, 1)
64
+ r = worker.work
65
+ assert_equal(["1", 2], r)
66
+ assert_equal(0, worker.failed_count)
67
+ end
45
68
 
46
- @worker.work
47
- assert_equal 1, @worker.failed_count
69
+ def test_work_custom_queue
70
+ p_queue = QC::Queue.new("priority_queue")
71
+ p_queue.enqueue("TestObject.two_args", "1", 2)
72
+ worker = TestWorker.new("priority_queue", 1, false, false, 1)
73
+ r = worker.work
74
+ assert_equal(["1", 2], r)
75
+ assert_equal(0, worker.failed_count)
48
76
  end
49
77
 
50
- test "only makes one connection" do
51
- QC.enqueue "TestNotifier.deliver", {}
52
- @worker.work
53
- assert_equal 1, @database.execute("SELECT count(*) from pg_stat_activity")[0]["count"].to_i,
78
+ def test_worker_ueses_one_conn
79
+ QC.enqueue("TestObject.no_args")
80
+ worker = TestWorker.new("default", 1, false, false, 1)
81
+ worker.work
82
+ assert_equal(
83
+ 1,
84
+ QC::Conn.execute("SELECT count(*) from pg_stat_activity")["count"].to_i,
54
85
  "Multiple connections -- Are there other connections in other terminals?"
86
+ )
55
87
  end
56
88
 
57
89
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: queue_classic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
5
- prerelease:
4
+ version: 2.0.0rc1
5
+ prerelease: 5
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ryan Smith
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-08-22 00:00:00.000000000Z
12
+ date: 2012-02-29 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg
16
- requirement: &15604460 !ruby/object:Gem::Requirement
16
+ requirement: &18736220 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.11.0
21
+ version: 0.13.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *15604460
24
+ version_requirements: *18736220
25
25
  description: queue_classic is a queueing library for Ruby apps. (Rails, Sinatra, Etc...)
26
26
  queue_classic features asynchronous job polling, database maintained locks and no
27
27
  ridiculous dependencies. As a matter of fact, queue_classic only requires pg.
@@ -31,21 +31,15 @@ extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
33
  - readme.md
34
+ - lib/queue_classic/conn.rb
34
35
  - lib/queue_classic/okjson.rb
35
- - lib/queue_classic/logger.rb
36
36
  - lib/queue_classic/worker.rb
37
- - lib/queue_classic/database.rb
38
- - lib/queue_classic/job.rb
39
37
  - lib/queue_classic/queue.rb
40
38
  - lib/queue_classic/tasks.rb
41
- - lib/queue_classic/durable_array.rb
39
+ - lib/queue_classic/queries.rb
42
40
  - lib/queue_classic.rb
43
- - test/database_test.rb
44
- - test/job_test.rb
45
- - test/database_helpers.rb
46
41
  - test/worker_test.rb
47
42
  - test/queue_test.rb
48
- - test/durable_array_test.rb
49
43
  - test/helper.rb
50
44
  homepage: http://github.com/ryandotsmith/queue_classic
51
45
  licenses: []
@@ -62,9 +56,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
62
56
  required_rubygems_version: !ruby/object:Gem::Requirement
63
57
  none: false
64
58
  requirements:
65
- - - ! '>='
59
+ - - ! '>'
66
60
  - !ruby/object:Gem::Version
67
- version: '0'
61
+ version: 1.3.1
68
62
  requirements: []
69
63
  rubyforge_project:
70
64
  rubygems_version: 1.8.10
@@ -72,8 +66,5 @@ signing_key:
72
66
  specification_version: 3
73
67
  summary: postgres backed queue
74
68
  test_files:
75
- - test/database_test.rb
76
- - test/job_test.rb
77
69
  - test/worker_test.rb
78
70
  - test/queue_test.rb
79
- - test/durable_array_test.rb
@@ -1,188 +0,0 @@
1
- module QC
2
- class Database
3
-
4
- @@connection = nil
5
-
6
- attr_reader :table_name
7
- attr_reader :top_boundary
8
-
9
- def initialize(queue_name=nil)
10
- log("initialized")
11
-
12
- @top_boundary = (ENV["QC_TOP_BOUND"] || 9).to_i
13
- log("top_boundary=#{@top_boundary}")
14
-
15
- @table_name = queue_name || "queue_classic_jobs"
16
- log("table_name=#{@table_name}")
17
-
18
- @channel_name = @table_name
19
- log("channel_name=#{@channel_name}")
20
-
21
- db_url = (ENV["QC_DATABASE_URL"] || ENV["DATABASE_URL"])
22
- @db_params = URI.parse(db_url)
23
- log("uri=#{db_url}")
24
- end
25
-
26
- def set_application_name
27
- execute("SET application_name = 'queue_classic'")
28
- end
29
-
30
- def escape(string)
31
- connection.escape(string)
32
- end
33
-
34
- def notify
35
- log("NOTIFY")
36
- execute("NOTIFY #{@channel_name}")
37
- end
38
-
39
- def listen
40
- log("LISTEN")
41
- execute("LISTEN #{@channel_name}")
42
- end
43
-
44
- def unlisten
45
- log("UNLISTEN")
46
- execute("UNLISTEN #{@channel_name}")
47
- end
48
-
49
- def drain_notify
50
- until connection.notifies.nil?
51
- log("draining notifications")
52
- end
53
- end
54
-
55
- def wait_for_notify(t)
56
- log("waiting for notify timeout=#{t}")
57
- connection.wait_for_notify(t) {|event, pid, msg| log("received notification #{event}")}
58
- log("done waiting for notify")
59
- end
60
-
61
- def transaction
62
- begin
63
- execute 'BEGIN'
64
- yield
65
- execute 'COMMIT'
66
- rescue Exception
67
- execute 'ROLLBACK'
68
- raise
69
- end
70
- end
71
-
72
- def transaction_idle?
73
- connection.transaction_status == PGconn::PQTRANS_IDLE
74
- end
75
-
76
- def execute(sql, *params)
77
- log("executing #{sql.inspect}, #{params.inspect}")
78
- begin
79
- params = nil if params.empty?
80
- connection.exec(sql, params)
81
- rescue PGError => e
82
- log("execute exception=#{e.inspect}")
83
- raise
84
- end
85
- end
86
-
87
- def connection
88
- @@connection ||= connect
89
- end
90
-
91
- def disconnect
92
- connection.finish
93
- @@connection = nil
94
- end
95
-
96
- def connect
97
- log("establishing connection")
98
- conn = PGconn.connect(
99
- @db_params.host,
100
- @db_params.port || 5432,
101
- nil, '', #opts, tty
102
- @db_params.path.gsub("/",""), # database name
103
- @db_params.user,
104
- @db_params.password
105
- )
106
- if conn.status != PGconn::CONNECTION_OK
107
- log("connection error=#{conn.error}")
108
- end
109
- conn
110
- end
111
-
112
- def load_functions
113
- execute(<<-EOD)
114
- -- We are declaring the return type to be queue_classic_jobs.
115
- -- This is ok since I am assuming that all of the users added queues will
116
- -- have identical columns to queue_classic_jobs.
117
- -- When QC supports queues with columns other than the default, we will have to change this.
118
-
119
- CREATE OR REPLACE FUNCTION lock_head(tname name, top_boundary integer) RETURNS SETOF queue_classic_jobs AS $$
120
- DECLARE
121
- unlocked integer;
122
- relative_top integer;
123
- job_count integer;
124
- BEGIN
125
- -- The purpose is to release contention for the first spot in the table.
126
- -- The select count(*) is going to slow down dequeue performance but allow
127
- -- for more workers. Would love to see some optimization here...
128
-
129
- EXECUTE 'SELECT count(*) FROM ' ||
130
- '(SELECT * FROM ' || quote_ident(tname) ||
131
- ' LIMIT ' || quote_literal(top_boundary) || ') limited'
132
- INTO job_count;
133
-
134
- SELECT TRUNC(random() * top_boundary + 1) INTO relative_top;
135
- IF job_count < top_boundary THEN
136
- relative_top = 0;
137
- END IF;
138
-
139
- LOOP
140
- BEGIN
141
- EXECUTE 'SELECT id FROM '
142
- || quote_ident(tname)
143
- || ' WHERE locked_at IS NULL'
144
- || ' ORDER BY id ASC'
145
- || ' LIMIT 1'
146
- || ' OFFSET ' || quote_literal(relative_top)
147
- || ' FOR UPDATE NOWAIT'
148
- INTO unlocked;
149
- EXIT;
150
- EXCEPTION
151
- WHEN lock_not_available THEN
152
- -- do nothing. loop again and hope we get a lock
153
- END;
154
- END LOOP;
155
-
156
- RETURN QUERY EXECUTE 'UPDATE '
157
- || quote_ident(tname)
158
- || ' SET locked_at = (CURRENT_TIMESTAMP)'
159
- || ' WHERE id = $1'
160
- || ' AND locked_at is NULL'
161
- || ' RETURNING *'
162
- USING unlocked;
163
-
164
- RETURN;
165
- END;
166
- $$ LANGUAGE plpgsql;
167
-
168
- CREATE OR REPLACE FUNCTION lock_head(tname varchar) RETURNS SETOF queue_classic_jobs AS $$
169
- BEGIN
170
- RETURN QUERY EXECUTE 'SELECT * FROM lock_head($1,10)' USING tname;
171
- END;
172
- $$ LANGUAGE plpgsql;
173
- EOD
174
- end
175
-
176
- def unload_functions
177
- execute(<<-EOD)
178
- DROP FUNCTION IF EXISTS lock_head(tname varchar);
179
- DROP FUNCTION IF EXISTS lock_head(tname name, top_boundary integer);
180
- EOD
181
- end
182
-
183
- def log(msg)
184
- Logger.puts(["database", msg].join(" "))
185
- end
186
-
187
- end
188
- end