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.
@@ -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