queue_classic 2.0.0rc1 → 2.0.0rc4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/queue_classic/okjson.rb +40 -15
- data/lib/queue_classic.rb +17 -12
- data/readme.md +31 -53
- data/sql/ddl.sql +66 -0
- data/sql/drop_ddl.sql +2 -0
- metadata +6 -4
data/lib/queue_classic/okjson.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module QC
|
2
|
-
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# Copyright 2011, 2012 Keith Rarick
|
3
5
|
#
|
4
6
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
7
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -261,6 +263,12 @@ module OkJson
|
|
261
263
|
def unquote(q)
|
262
264
|
q = q[1...-1]
|
263
265
|
a = q.dup # allocate a big enough string
|
266
|
+
rubydoesenc = false
|
267
|
+
# In ruby >= 1.9, a[w] is a codepoint, not a byte.
|
268
|
+
if a.class.method_defined?(:force_encoding)
|
269
|
+
a.force_encoding('UTF-8')
|
270
|
+
rubydoesenc = true
|
271
|
+
end
|
264
272
|
r, w = 0, 0
|
265
273
|
while r < q.length
|
266
274
|
c = q[r]
|
@@ -298,7 +306,12 @@ module OkJson
|
|
298
306
|
end
|
299
307
|
end
|
300
308
|
end
|
301
|
-
|
309
|
+
if rubydoesenc
|
310
|
+
a[w] = '' << uchar
|
311
|
+
w += 1
|
312
|
+
else
|
313
|
+
w += ucharenc(a, w, uchar)
|
314
|
+
end
|
302
315
|
else
|
303
316
|
raise Error, "invalid escape char #{q[r]} in \"#{q}\""
|
304
317
|
end
|
@@ -308,6 +321,8 @@ module OkJson
|
|
308
321
|
# Copy anything else byte-for-byte.
|
309
322
|
# Valid UTF-8 will remain valid UTF-8.
|
310
323
|
# Invalid UTF-8 will remain invalid UTF-8.
|
324
|
+
# In ruby >= 1.9, c is a codepoint, not a byte,
|
325
|
+
# in which case this is still what we want.
|
311
326
|
a[w] = c
|
312
327
|
r += 1
|
313
328
|
w += 1
|
@@ -442,6 +457,10 @@ module OkJson
|
|
442
457
|
t = StringIO.new
|
443
458
|
t.putc(?")
|
444
459
|
r = 0
|
460
|
+
|
461
|
+
# In ruby >= 1.9, s[r] is a codepoint, not a byte.
|
462
|
+
rubydoesenc = s.class.method_defined?(:encoding)
|
463
|
+
|
445
464
|
while r < s.length
|
446
465
|
case s[r]
|
447
466
|
when ?" then t.print('\\"')
|
@@ -456,21 +475,13 @@ module OkJson
|
|
456
475
|
case true
|
457
476
|
when Spc <= c && c <= ?~
|
458
477
|
t.putc(c)
|
459
|
-
when
|
478
|
+
when rubydoesenc
|
479
|
+
u = c.ord
|
480
|
+
surrenc(t, u)
|
481
|
+
else
|
460
482
|
u, size = uchardec(s, r)
|
461
483
|
r += size - 1 # we add one more at the bottom of the loop
|
462
|
-
|
463
|
-
t.print('\\u')
|
464
|
-
hexenc4(t, u)
|
465
|
-
else
|
466
|
-
u1, u2 = unsubst(u)
|
467
|
-
t.print('\\u')
|
468
|
-
hexenc4(t, u1)
|
469
|
-
t.print('\\u')
|
470
|
-
hexenc4(t, u2)
|
471
|
-
end
|
472
|
-
else
|
473
|
-
# invalid byte; skip it
|
484
|
+
surrenc(t, u)
|
474
485
|
end
|
475
486
|
end
|
476
487
|
r += 1
|
@@ -480,6 +491,20 @@ module OkJson
|
|
480
491
|
end
|
481
492
|
|
482
493
|
|
494
|
+
def surrenc(t, u)
|
495
|
+
if u < 0x10000
|
496
|
+
t.print('\\u')
|
497
|
+
hexenc4(t, u)
|
498
|
+
else
|
499
|
+
u1, u2 = unsubst(u)
|
500
|
+
t.print('\\u')
|
501
|
+
hexenc4(t, u1)
|
502
|
+
t.print('\\u')
|
503
|
+
hexenc4(t, u2)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
|
483
508
|
def hexenc4(t, u)
|
484
509
|
t.putc(Hex[(u>>12)&0xf])
|
485
510
|
t.putc(Hex[(u>>8)&0xf])
|
data/lib/queue_classic.rb
CHANGED
@@ -13,24 +13,25 @@ require "queue_classic/worker"
|
|
13
13
|
|
14
14
|
module QC
|
15
15
|
|
16
|
-
Root = File.expand_path(File.dirname(__FILE__))
|
16
|
+
Root = File.expand_path("..", File.dirname(__FILE__))
|
17
17
|
SqlFunctions = File.join(QC::Root, "/sql/ddl.sql")
|
18
18
|
DropSqlFunctions = File.join(QC::Root, "/sql/drop_ddl.sql")
|
19
19
|
|
20
|
-
Log = Logger.new($stdout)
|
21
|
-
Log.level = (ENV["QC_LOG_LEVEL"] || Logger::DEBUG).to_i
|
22
|
-
Log.info("program=queue_classic log=true")
|
23
|
-
|
24
20
|
DB_URL =
|
25
21
|
ENV["QC_DATABASE_URL"] ||
|
26
22
|
ENV["DATABASE_URL"] ||
|
27
23
|
raise(ArgumentError, "missing QC_DATABASE_URL or DATABASE_URL")
|
28
24
|
|
25
|
+
# export QC_LOG_LEVEL=`ruby -r "logger" -e "puts Logger::ERROR"`
|
26
|
+
LOG_LEVEL = (ENV["QC_LOG_LEVEL"] || Logger::DEBUG).to_i
|
27
|
+
|
29
28
|
# You can use the APP_NAME to query for
|
30
29
|
# postgres related process information in the
|
31
30
|
# pg_stat_activity table. Don't set this unless
|
32
31
|
# you are using PostgreSQL > 9.0
|
33
|
-
APP_NAME = ENV["QC_APP_NAME"]
|
32
|
+
if APP_NAME = ENV["QC_APP_NAME"]
|
33
|
+
Conn.execute("SET application_name = '#{APP_NAME}'")
|
34
|
+
end
|
34
35
|
|
35
36
|
# Why do you want to change the table name?
|
36
37
|
# Just deal with the default OK?
|
@@ -55,20 +56,24 @@ module QC
|
|
55
56
|
|
56
57
|
# Set this variable if you wish for
|
57
58
|
# the worker to fork a UNIX process for
|
58
|
-
# each locked job. Remember to
|
59
|
-
# any database
|
59
|
+
# each locked job. Remember to re-establish
|
60
|
+
# any database connections. See the worker
|
60
61
|
# for more details.
|
61
62
|
FORK_WORKER = !ENV["QC_FORK_WORKER"].nil?
|
62
63
|
|
63
|
-
# The worker uses an exponential
|
64
|
+
# The worker uses an exponential back-off
|
64
65
|
# algorithm to lock a job. This value will be used
|
65
66
|
# as the max exponent.
|
66
67
|
MAX_LOCK_ATTEMPTS = (ENV["QC_MAX_LOCK_ATTEMPTS"] || 5).to_i
|
67
68
|
|
68
|
-
if APP_NAME
|
69
|
-
Conn.execute("SET application_name = '#{APP_NAME}'")
|
70
|
-
end
|
71
69
|
|
70
|
+
# Setup the logger
|
71
|
+
Log = Logger.new($stdout)
|
72
|
+
Log.level = LOG_LEVEL
|
73
|
+
Log.info("program=queue_classic log=true")
|
74
|
+
|
75
|
+
# Defer method calls on the QC module to the
|
76
|
+
# default queue. This facilitates QC.enqueue()
|
72
77
|
def self.method_missing(sym, *args, &block)
|
73
78
|
default_queue.send(sym, *args, &block)
|
74
79
|
end
|
data/readme.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# queue_classic
|
2
2
|
|
3
|
-
v2.0.
|
3
|
+
v2.0.0rc2
|
4
4
|
|
5
5
|
queue_classic is a PostgreSQL-backed queueing library that is focused on
|
6
6
|
concurrent job locking, minimizing database load & providing a simple &
|
@@ -139,49 +139,9 @@ end
|
|
139
139
|
|
140
140
|
## Configure
|
141
141
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
$QC_LOG_LEVEL
|
146
|
-
|
147
|
-
# Specifies the database that queue_classic will rely upon.
|
148
|
-
# queue_classic will try and use QC_DATABASE_URL before it uses DATABASE_URL.
|
149
|
-
$QC_DATABASE_URL
|
150
|
-
$DATABASE_URL
|
151
|
-
|
152
|
-
# Fuzzy-FIFO
|
153
|
-
# For strict FIFO set to 1. Otherwise, worker will
|
154
|
-
# attempt to lock a job in this top region.
|
155
|
-
# Default: 9
|
156
|
-
$QC_TOP_BOUND
|
157
|
-
|
158
|
-
# If you want your worker to fork a new
|
159
|
-
# UNIX process for each job, set this var to 'true'
|
160
|
-
#
|
161
|
-
# Default: false
|
162
|
-
$QC_FORK_WORKER
|
163
|
-
|
164
|
-
# The worker uses an exp backoff algorithm
|
165
|
-
# if you want high throughput don't use Kernel.sleep
|
166
|
-
# use LISTEN/NOTIFY sleep. When set to true, the worker's
|
167
|
-
# sleep will be preempted by insertion into the queue.
|
168
|
-
#
|
169
|
-
# Default: false
|
170
|
-
$QC_LISTENING_WORKER
|
171
|
-
|
172
|
-
# The worker uses an exp backoff algorithm. The base of
|
173
|
-
# the exponent is 2. This var determines the max power of the exp.
|
174
|
-
#
|
175
|
-
# Default: 5 which implies max sleep time of 2^(5-1) => 16 seconds
|
176
|
-
$QC_MAX_LOCK_ATTEMPTS
|
177
|
-
|
178
|
-
# This var is important for consumers of the queue.
|
179
|
-
# If you have configured many queues, this var will
|
180
|
-
# instruct the worker to bind to a particular queue.
|
181
|
-
#
|
182
|
-
# Default: queue_classic_jobs
|
183
|
-
$QUEUE
|
184
|
-
```
|
142
|
+
All configuration takes place in the form of environment vars.
|
143
|
+
See [queue_classic.rb](https://github.com/ryandotsmith/queue_classic/blob/master/lib/queue_classic.rb#L29-66)
|
144
|
+
for a list of options.
|
185
145
|
|
186
146
|
## Usage
|
187
147
|
|
@@ -217,7 +177,7 @@ QC.enqueue("Kernel.printf", "hello %s", "world")
|
|
217
177
|
# This method has a hash argument.
|
218
178
|
QC.enqueue("Kernel.puts", {"hello" => "world"})
|
219
179
|
|
220
|
-
# This method has a
|
180
|
+
# This method has a array argument.
|
221
181
|
QC.enqueue("Kernel.puts", ["hello", "world"])
|
222
182
|
```
|
223
183
|
|
@@ -258,22 +218,25 @@ p_queue.enqueue("Kernel.printf", "hello %s", "world")
|
|
258
218
|
# This method has a hash argument.
|
259
219
|
p_queue.enqueue("Kernel.puts", {"hello" => "world"})
|
260
220
|
|
261
|
-
# This method has a
|
221
|
+
# This method has a array argument.
|
262
222
|
p_queue.enqueue("Kernel.puts", ["hello", "world"])
|
263
223
|
```
|
264
224
|
|
265
225
|
This code example shows how to produce jobs into a custom queue,
|
266
|
-
to consume jobs from the
|
226
|
+
to consume jobs from the custome queue be sure and set the `$QUEUE`
|
267
227
|
var to the q_name in the worker's UNIX environment.
|
268
228
|
|
269
229
|
### Consumer
|
270
230
|
|
271
|
-
|
272
|
-
|
273
|
-
|
231
|
+
There are several approaches to working jobs. The first is to include
|
232
|
+
a task file provided by queue_classic and the other approach is to
|
233
|
+
write a custom bin file.
|
274
234
|
|
275
235
|
#### Rake Task
|
276
236
|
|
237
|
+
Be sure to include `queue_classic` and `queue_classic/tasks`
|
238
|
+
in your primary Rakefile.
|
239
|
+
|
277
240
|
To work jobs from the default queue:
|
278
241
|
|
279
242
|
```bash
|
@@ -287,9 +250,8 @@ $ QUEUE="p_queue" bundle exec rake qc:work
|
|
287
250
|
|
288
251
|
#### Bin File
|
289
252
|
|
290
|
-
|
291
|
-
|
292
|
-
in your project's root directory. Then add a file called worker.
|
253
|
+
Start by making a bin directory in your project's root directory.
|
254
|
+
Then add an executable file called worker.
|
293
255
|
|
294
256
|
**bin/worker**
|
295
257
|
|
@@ -549,6 +511,22 @@ clock: clockwork clock.rb
|
|
549
511
|
|
550
512
|
## Upgrading From Older Versions
|
551
513
|
|
514
|
+
### 1.X to 2.X
|
515
|
+
|
516
|
+
#### Database Schema Changes
|
517
|
+
|
518
|
+
* all queues are in 1 table with a q_name column
|
519
|
+
* table includes a method column and an args column
|
520
|
+
|
521
|
+
#### Producer Changes
|
522
|
+
|
523
|
+
* initializing a Queue instance takes a column name instead of a table name
|
524
|
+
|
525
|
+
#### Consumer Changes
|
526
|
+
|
527
|
+
* all of the worker configuratoin is passed in through the initializer
|
528
|
+
* rake task uses data from env vars to initialize a worker
|
529
|
+
|
552
530
|
### 0.2.X to 0.3.X
|
553
531
|
|
554
532
|
* Deprecated QC.queue_length in favor of QC.length
|
data/sql/ddl.sql
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
-- We are declaring the return type to be queue_classic_jobs.
|
2
|
+
-- This is ok since I am assuming that all of the users added queues will
|
3
|
+
-- have identical columns to queue_classic_jobs.
|
4
|
+
-- When QC supports queues with columns other than the default, we will have to change this.
|
5
|
+
|
6
|
+
CREATE OR REPLACE FUNCTION lock_head(q_name varchar, top_boundary integer)
|
7
|
+
RETURNS SETOF queue_classic_jobs AS $$
|
8
|
+
DECLARE
|
9
|
+
unlocked integer;
|
10
|
+
relative_top integer;
|
11
|
+
job_count integer;
|
12
|
+
BEGIN
|
13
|
+
-- The purpose is to release contention for the first spot in the table.
|
14
|
+
-- The select count(*) is going to slow down dequeue performance but allow
|
15
|
+
-- for more workers. Would love to see some optimization here...
|
16
|
+
|
17
|
+
EXECUTE 'SELECT count(*) FROM '
|
18
|
+
|| '(SELECT * FROM queue_classic_jobs WHERE q_name = '
|
19
|
+
|| quote_literal(q_name)
|
20
|
+
|| ' LIMIT '
|
21
|
+
|| quote_literal(top_boundary)
|
22
|
+
|| ') limited'
|
23
|
+
INTO job_count;
|
24
|
+
|
25
|
+
SELECT TRUNC(random() * (top_boundary - 1))
|
26
|
+
INTO relative_top;
|
27
|
+
|
28
|
+
IF job_count < top_boundary THEN
|
29
|
+
relative_top = 0;
|
30
|
+
END IF;
|
31
|
+
|
32
|
+
LOOP
|
33
|
+
BEGIN
|
34
|
+
EXECUTE 'SELECT id FROM queue_classic_jobs '
|
35
|
+
|| ' WHERE locked_at IS NULL'
|
36
|
+
|| ' AND q_name = '
|
37
|
+
|| quote_literal(q_name)
|
38
|
+
|| ' ORDER BY id ASC'
|
39
|
+
|| ' LIMIT 1'
|
40
|
+
|| ' OFFSET ' || quote_literal(relative_top)
|
41
|
+
|| ' FOR UPDATE NOWAIT'
|
42
|
+
INTO unlocked;
|
43
|
+
EXIT;
|
44
|
+
EXCEPTION
|
45
|
+
WHEN lock_not_available THEN
|
46
|
+
-- do nothing. loop again and hope we get a lock
|
47
|
+
END;
|
48
|
+
END LOOP;
|
49
|
+
|
50
|
+
RETURN QUERY EXECUTE 'UPDATE queue_classic_jobs '
|
51
|
+
|| ' SET locked_at = (CURRENT_TIMESTAMP)'
|
52
|
+
|| ' WHERE id = $1'
|
53
|
+
|| ' AND locked_at is NULL'
|
54
|
+
|| ' RETURNING *'
|
55
|
+
USING unlocked;
|
56
|
+
|
57
|
+
RETURN;
|
58
|
+
END;
|
59
|
+
$$ LANGUAGE plpgsql;
|
60
|
+
|
61
|
+
CREATE OR REPLACE FUNCTION lock_head(tname varchar)
|
62
|
+
RETURNS SETOF queue_classic_jobs AS $$
|
63
|
+
BEGIN
|
64
|
+
RETURN QUERY EXECUTE 'SELECT * FROM lock_head($1,10)' USING tname;
|
65
|
+
END;
|
66
|
+
$$ LANGUAGE plpgsql;
|
data/sql/drop_ddl.sql
ADDED
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: queue_classic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.0rc4
|
5
5
|
prerelease: 5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
- Ryan Smith
|
8
|
+
- Ryan Smith (ace hacker)
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
@@ -13,7 +13,7 @@ date: 2012-02-29 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg
|
16
|
-
requirement: &
|
16
|
+
requirement: &8948440 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: 0.13.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *8948440
|
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,6 +31,8 @@ extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
33
|
- readme.md
|
34
|
+
- sql/ddl.sql
|
35
|
+
- sql/drop_ddl.sql
|
34
36
|
- lib/queue_classic/conn.rb
|
35
37
|
- lib/queue_classic/okjson.rb
|
36
38
|
- lib/queue_classic/worker.rb
|