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.
@@ -1,5 +1,7 @@
1
1
  module QC
2
- # Copyright 2011 Keith Rarick
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
- w += ucharenc(a, w, uchar)
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 true
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
- if u < 0x10000
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 restablish
59
- # any database connectoins. See the worker
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 backoff
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.0rc1
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
- ```bash
143
- # Log level.
144
- # export QC_LOG_LEVEL=`ruby -r "logger" -e "puts Logger::ERROR"`
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 hash argument.
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 hash argument.
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 customer queue be sure and set the `$QUEUE`
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
- Now that you have some jobs in your queue, you probably want to work them.
272
- Let's find out how... If you are using a Rakefile and have included `queue_classic/tasks`
273
- then you can enter the following command to start a worker:
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
- The approach that I take when building simple ruby programs and sinatra apps is to
291
- create an executable file that starts the worker. Start by making a bin directory
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
@@ -0,0 +1,2 @@
1
+ DROP FUNCTION IF EXISTS lock_head(tname varchar);
2
+ DROP FUNCTION IF EXISTS lock_head(tname name, top_boundary integer);
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.0rc1
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: &18736220 !ruby/object:Gem::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: *18736220
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