queue_classic 1.0.2 → 2.0.0rc1

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/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