queue_classic 2.0.0rc1 → 2.0.0rc4
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/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
|