reliable-msg 1.0.1 → 1.1.0
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/README +5 -1
- data/Rakefile +30 -23
- data/changelog.txt +32 -0
- data/lib/reliable-msg.rb +10 -5
- data/lib/reliable-msg/cli.rb +47 -3
- data/lib/reliable-msg/client.rb +213 -0
- data/lib/reliable-msg/message-store.rb +128 -49
- data/lib/reliable-msg/mysql.sql +7 -1
- data/lib/reliable-msg/queue-manager.rb +263 -58
- data/lib/reliable-msg/queue.rb +100 -253
- data/lib/reliable-msg/rails.rb +114 -0
- data/lib/reliable-msg/selector.rb +65 -75
- data/lib/reliable-msg/topic.rb +215 -0
- data/test/test-queue.rb +35 -5
- data/test/test-rails.rb +59 -0
- data/test/test-topic.rb +102 -0
- metadata +54 -41
- data/lib/uuid.rb +0 -384
- data/test/test-uuid.rb +0 -48
@@ -2,26 +2,24 @@
|
|
2
2
|
# = message-store.rb - Queue manager storage adapters
|
3
3
|
#
|
4
4
|
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
-
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/
|
5
|
+
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
6
6
|
# Copyright:: Copyright (c) 2005 Assaf Arkin
|
7
7
|
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
8
|
#
|
9
9
|
#--
|
10
|
-
# Changes:
|
11
|
-
# 11/11/05
|
12
|
-
# Fixed: Messages retrieved in order after queue manager recovers when using MySQL.
|
13
10
|
#++
|
14
11
|
|
15
12
|
require 'thread'
|
16
|
-
require '
|
17
|
-
require 'reliable-msg/queue'
|
13
|
+
require 'reliable-msg/client'
|
18
14
|
|
19
15
|
module ReliableMsg
|
20
16
|
|
17
|
+
|
21
18
|
module MessageStore
|
22
19
|
|
23
20
|
ERROR_INVALID_MESSAGE_STORE = "No message store '%s' available (note: case is not important)" #:nodoc:
|
24
21
|
|
22
|
+
|
25
23
|
# Base class for message store.
|
26
24
|
class Base
|
27
25
|
|
@@ -29,10 +27,12 @@ module ReliableMsg
|
|
29
27
|
|
30
28
|
@@stores = {} #:nodoc:
|
31
29
|
|
30
|
+
|
32
31
|
def initialize logger
|
33
32
|
@logger = logger
|
34
33
|
end
|
35
34
|
|
35
|
+
|
36
36
|
# Returns the message store type name.
|
37
37
|
#
|
38
38
|
# :call-seq:
|
@@ -42,6 +42,7 @@ module ReliableMsg
|
|
42
42
|
raise RuntimeException, "Not implemented"
|
43
43
|
end
|
44
44
|
|
45
|
+
|
45
46
|
# Set up the message store. Create files, database tables, etc.
|
46
47
|
#
|
47
48
|
# :call-seq:
|
@@ -50,6 +51,7 @@ module ReliableMsg
|
|
50
51
|
def setup
|
51
52
|
end
|
52
53
|
|
54
|
+
|
53
55
|
# Returns the message store configuration as a hash.
|
54
56
|
#
|
55
57
|
# :call-seq:
|
@@ -59,6 +61,7 @@ module ReliableMsg
|
|
59
61
|
raise RuntimeException, "Not implemented"
|
60
62
|
end
|
61
63
|
|
64
|
+
|
62
65
|
# Activates the message store. Call this method before using the
|
63
66
|
# message store.
|
64
67
|
#
|
@@ -68,10 +71,12 @@ module ReliableMsg
|
|
68
71
|
def activate
|
69
72
|
@mutex = Mutex.new
|
70
73
|
@queues = {Queue::DLQ=>[]}
|
74
|
+
@topics = {}
|
71
75
|
@cache = {}
|
72
76
|
# TODO: add recovery logic
|
73
77
|
end
|
74
78
|
|
79
|
+
|
75
80
|
# Deactivates the message store. Call this method when done using
|
76
81
|
# the message store.
|
77
82
|
#
|
@@ -79,9 +84,10 @@ module ReliableMsg
|
|
79
84
|
# store.deactivate
|
80
85
|
#
|
81
86
|
def deactivate
|
82
|
-
@mutex = @queues = @cache = nil
|
87
|
+
@mutex = @queues = @topics = @cache = nil
|
83
88
|
end
|
84
89
|
|
90
|
+
|
85
91
|
def transaction &block
|
86
92
|
result = block.call inserts = [], deletes = [], dlqs= []
|
87
93
|
begin
|
@@ -92,26 +98,44 @@ module ReliableMsg
|
|
92
98
|
# Empty the cache and reload the queue, before raising the error.
|
93
99
|
@cache = {}
|
94
100
|
@queues = {Queue::DLQ=>[]}
|
101
|
+
@topics = {}
|
95
102
|
load_index
|
96
103
|
raise error
|
97
104
|
end
|
98
105
|
result
|
99
106
|
end
|
100
107
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
if
|
108
|
+
|
109
|
+
def get_message queue, &block
|
110
|
+
messages = @queues[queue]
|
111
|
+
return nil unless messages
|
112
|
+
messages.each do |headers|
|
113
|
+
if block.call(headers)
|
107
114
|
id = headers[:id]
|
108
|
-
message = @cache[id] || load(id, queue)
|
115
|
+
message = @cache[id] || load(id, :queue, queue)
|
109
116
|
return {:id=>id, :headers=>headers, :message=>message}
|
110
117
|
end
|
111
118
|
end
|
112
119
|
return nil
|
113
120
|
end
|
114
121
|
|
122
|
+
|
123
|
+
def get_headers queue
|
124
|
+
return @queues[queue] || []
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def get_last topic, seen, &block
|
129
|
+
headers = @topics[topic]
|
130
|
+
return nil if headers.nil? || headers[:id] == seen
|
131
|
+
if block.call(headers)
|
132
|
+
id = headers[:id]
|
133
|
+
message = @cache[id] || load(id, :topic, topic)
|
134
|
+
{:id=>id, :headers=>headers, :message=>message}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
115
139
|
# Returns a message store from the specified configuration (previously
|
116
140
|
# created with configure).
|
117
141
|
#
|
@@ -125,31 +149,40 @@ module ReliableMsg
|
|
125
149
|
cls.new config, logger
|
126
150
|
end
|
127
151
|
|
152
|
+
|
128
153
|
protected
|
129
154
|
|
130
155
|
def update inserts, deletes, dlqs
|
131
156
|
@mutex.synchronize do
|
132
157
|
inserts.each do |insert|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
queue[idx
|
141
|
-
|
142
|
-
|
158
|
+
if insert[:queue]
|
159
|
+
queue = @queues[insert[:queue]] ||= []
|
160
|
+
headers = insert[:headers]
|
161
|
+
# Add element based on priority, higher priority comes first.
|
162
|
+
priority = headers[:priority]
|
163
|
+
added = false
|
164
|
+
queue.each_index do |idx|
|
165
|
+
if queue[idx][:priority] < priority
|
166
|
+
queue[idx, 0] = headers
|
167
|
+
added = true
|
168
|
+
break
|
169
|
+
end
|
143
170
|
end
|
171
|
+
queue << headers unless added
|
172
|
+
@cache[insert[:id]] = insert[:message]
|
173
|
+
elsif insert[:topic]
|
174
|
+
@topics[insert[:topic]] = insert[:headers]
|
144
175
|
end
|
145
|
-
queue << headers unless added
|
146
|
-
@cache[insert[:id]] = insert[:message]
|
147
176
|
end
|
148
177
|
deletes.each do |delete|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
178
|
+
if delete[:queue]
|
179
|
+
queue = @queues[delete[:queue]]
|
180
|
+
id = delete[:id]
|
181
|
+
queue.delete_if { |headers| headers[:id] == id }
|
182
|
+
@cache.delete id
|
183
|
+
elsif delete[:topic]
|
184
|
+
@topics.delete delete[:topic]
|
185
|
+
end
|
153
186
|
end
|
154
187
|
dlqs.each do |dlq|
|
155
188
|
queue = @queues[dlq[:queue]]
|
@@ -185,6 +218,11 @@ module ReliableMsg
|
|
185
218
|
"path"=>DEFAULT_PATH
|
186
219
|
}
|
187
220
|
|
221
|
+
ERROR_PATH_NOT_DIR = "The path %s is not a directory" #:nodoc:
|
222
|
+
|
223
|
+
ERROR_FAILED_WRITE_MASTER = "Cannot write to master index file %s" #:nodoc:
|
224
|
+
|
225
|
+
|
188
226
|
def initialize config, logger
|
189
227
|
super logger
|
190
228
|
@fsync = config['fsync']
|
@@ -197,13 +235,15 @@ module ReliableMsg
|
|
197
235
|
@path = File.expand_path(config['path'] || DEFAULT_PATH)
|
198
236
|
end
|
199
237
|
|
238
|
+
|
200
239
|
def type
|
201
240
|
TYPE
|
202
241
|
end
|
203
242
|
|
243
|
+
|
204
244
|
def setup
|
205
245
|
if File.exist?(@path)
|
206
|
-
raise RuntimeError,
|
246
|
+
raise RuntimeError, format(ERROR_PATH_NOT_DIR, @path) unless File.directory?(@path)
|
207
247
|
false
|
208
248
|
else
|
209
249
|
Dir.mkdir @path
|
@@ -211,17 +251,19 @@ module ReliableMsg
|
|
211
251
|
end
|
212
252
|
end
|
213
253
|
|
254
|
+
|
214
255
|
def configuration
|
215
256
|
{ "type"=>TYPE, "path"=>@path }
|
216
257
|
end
|
217
258
|
|
259
|
+
|
218
260
|
def activate
|
219
261
|
super
|
220
262
|
Dir.mkdir @path unless File.exist?(@path)
|
221
|
-
raise RuntimeError,
|
263
|
+
raise RuntimeError, format(ERROR_PATH_NOT_DIR, @path) unless File.directory?(@path)
|
222
264
|
index = "#{@path}/master.idx"
|
223
265
|
if File.exist? index
|
224
|
-
raise RuntimeError,
|
266
|
+
raise RuntimeError, format(ERROR_FAILED_WRITE_MASTER, index) unless File.writable?(index)
|
225
267
|
@file = File.open index, "r+"
|
226
268
|
@file.flock File::LOCK_EX
|
227
269
|
@file.binmode # Things break if you forget binmode on Windows.
|
@@ -237,7 +279,9 @@ module ReliableMsg
|
|
237
279
|
end
|
238
280
|
end
|
239
281
|
|
282
|
+
|
240
283
|
def deactivate
|
284
|
+
@file.flock File::LOCK_UN
|
241
285
|
@file.close
|
242
286
|
@file_map.each_pair do |id, map|
|
243
287
|
map[1].close if map[1]
|
@@ -249,6 +293,7 @@ module ReliableMsg
|
|
249
293
|
super
|
250
294
|
end
|
251
295
|
|
296
|
+
|
252
297
|
protected
|
253
298
|
|
254
299
|
def update inserts, deletes, dlqs
|
@@ -271,7 +316,8 @@ module ReliableMsg
|
|
271
316
|
# (message and file have different IDs).
|
272
317
|
file.sysseek 0, IO::SEEK_SET
|
273
318
|
file.syswrite insert[:message]
|
274
|
-
file.
|
319
|
+
file.flush
|
320
|
+
file.truncate insert[:message].length
|
275
321
|
file.flush
|
276
322
|
@mutex.synchronize do
|
277
323
|
@file_map[insert[:id]] = [name, file]
|
@@ -301,7 +347,7 @@ module ReliableMsg
|
|
301
347
|
file_map = {}
|
302
348
|
@file_map.each_pair { |id, name_file| file_map[id] = name_file[0] }
|
303
349
|
file_free = @file_free.collect { |name_file| name_file[0] }
|
304
|
-
image = Marshal::dump({:queues=>@queues, :file_map=>file_map, :file_free=>file_free})
|
350
|
+
image = Marshal::dump({:queues=>@queues, :topics=>@topics, :file_map=>file_map, :file_free=>file_free})
|
305
351
|
length = image.length
|
306
352
|
# Determine if we can store the new image before the last one (must have
|
307
353
|
# enough space from header), or append it to the end of the last one.
|
@@ -323,6 +369,7 @@ module ReliableMsg
|
|
323
369
|
end
|
324
370
|
end
|
325
371
|
|
372
|
+
|
326
373
|
def load_index
|
327
374
|
@file.sysseek 0, IO::SEEK_SET
|
328
375
|
last_block = @file.sysread(8).hex
|
@@ -332,17 +379,19 @@ module ReliableMsg
|
|
332
379
|
# master index.
|
333
380
|
@file.sysseek last_block, IO::SEEK_SET
|
334
381
|
length = @file.sysread(8).hex
|
335
|
-
# Load the index image and create the queues, file_free
|
336
|
-
# file_map structures.
|
382
|
+
# Load the index image and create the queues, topics, file_free
|
383
|
+
# and file_map structures.
|
337
384
|
image = Marshal::load @file.sysread(length)
|
338
385
|
@queues = image[:queues]
|
386
|
+
@topics = image[:topics]
|
339
387
|
image[:file_free].each { |name| @file_free << [name, nil] }
|
340
388
|
image[:file_map].each_pair { |id, name| @file_map[id] = [name, nil] }
|
341
389
|
@last_block, @last_block_end = last_block, last_block + length + 8
|
342
390
|
end
|
343
391
|
end
|
344
392
|
|
345
|
-
|
393
|
+
|
394
|
+
def load id, type, queue
|
346
395
|
# Find the file from the message/file mapping.
|
347
396
|
map = @file_map[id]
|
348
397
|
return nil unless map # TODO: Error?
|
@@ -371,6 +420,7 @@ module ReliableMsg
|
|
371
420
|
require 'active_record/vendor/mysql'
|
372
421
|
end
|
373
422
|
|
423
|
+
|
374
424
|
class MySQL < Base #:nodoc:
|
375
425
|
|
376
426
|
TYPE = self.name.split('::').last.downcase
|
@@ -383,29 +433,31 @@ module ReliableMsg
|
|
383
433
|
# Reference to an open MySQL connection held in the current thread.
|
384
434
|
THREAD_CURRENT_MYSQL = :reliable_msg_mysql #:nodoc:
|
385
435
|
|
436
|
+
|
386
437
|
def initialize config, logger
|
387
438
|
super logger
|
388
439
|
@config = { :host=>config['host'], :username=>config['username'], :password=>config['password'],
|
389
440
|
:database=>config['database'], :port=>config['port'], :socket=>config['socket'] }
|
390
441
|
@prefix = config['prefix'] || DEFAULT_PREFIX
|
391
442
|
@queues_table = "#{@prefix}queues"
|
443
|
+
@topics_table = "#{@prefix}topics"
|
392
444
|
end
|
393
445
|
|
446
|
+
|
394
447
|
def type
|
395
448
|
TYPE
|
396
449
|
end
|
397
450
|
|
451
|
+
|
398
452
|
def setup
|
399
453
|
mysql = connection
|
400
|
-
|
454
|
+
requires = 2 # Number of tables used by reliable-msg.
|
401
455
|
mysql.query "SHOW TABLES" do |result|
|
402
456
|
while row = result.fetch_row
|
403
|
-
|
457
|
+
requires -= 1 if row[0] == @queues_table || row[0] == @topics_table
|
404
458
|
end
|
405
459
|
end
|
406
|
-
if
|
407
|
-
false
|
408
|
-
else
|
460
|
+
if requires > 0
|
409
461
|
sql = File.open File.join(File.dirname(__FILE__), "mysql.sql"), "r" do |input|
|
410
462
|
input.readlines.join
|
411
463
|
end
|
@@ -415,6 +467,7 @@ module ReliableMsg
|
|
415
467
|
end
|
416
468
|
end
|
417
469
|
|
470
|
+
|
418
471
|
def configuration
|
419
472
|
config = { "type"=>TYPE, "host"=>@config[:host], "username"=>@config[:username],
|
420
473
|
"password"=>@config[:password], "database"=>@config[:database] }
|
@@ -424,11 +477,13 @@ module ReliableMsg
|
|
424
477
|
config
|
425
478
|
end
|
426
479
|
|
480
|
+
|
427
481
|
def activate
|
428
482
|
super
|
429
483
|
load_index
|
430
484
|
end
|
431
485
|
|
486
|
+
|
432
487
|
def deactivate
|
433
488
|
Thread.list.each do |thread|
|
434
489
|
if conn = thread[THREAD_CURRENT_MYSQL]
|
@@ -439,6 +494,7 @@ module ReliableMsg
|
|
439
494
|
super
|
440
495
|
end
|
441
496
|
|
497
|
+
|
442
498
|
protected
|
443
499
|
|
444
500
|
def update inserts, deletes, dlqs
|
@@ -446,9 +502,15 @@ module ReliableMsg
|
|
446
502
|
mysql.query "BEGIN"
|
447
503
|
begin
|
448
504
|
inserts.each do |insert|
|
449
|
-
|
505
|
+
if insert[:queue]
|
506
|
+
mysql.query "INSERT INTO `#{@queues_table}` (id,queue,headers,object) VALUES('#{connection.quote insert[:id]}','#{connection.quote insert[:queue]}',BINARY '#{connection.quote Marshal::dump(insert[:headers])}',BINARY '#{connection.quote insert[:message]}')"
|
507
|
+
else
|
508
|
+
mysql.query "REPLACE `#{@topics_table}` (topic,headers,object) VALUES('#{connection.quote insert[:topic]}',BINARY '#{connection.quote Marshal::dump(insert[:headers])}',BINARY '#{connection.quote insert[:message]}')"
|
509
|
+
end
|
510
|
+
end
|
511
|
+
ids = deletes.inject([]) do |array, delete|
|
512
|
+
delete[:queue] ? array << "'#{delete[:id]}'" : array
|
450
513
|
end
|
451
|
-
ids = deletes.collect {|delete| "'#{delete[:id]}'" }
|
452
514
|
if !ids.empty?
|
453
515
|
mysql.query "DELETE FROM `#{@queues_table}` WHERE id IN (#{ids.join ','})"
|
454
516
|
end
|
@@ -463,6 +525,7 @@ module ReliableMsg
|
|
463
525
|
super
|
464
526
|
end
|
465
527
|
|
528
|
+
|
466
529
|
def load_index
|
467
530
|
connection.query "SELECT id,queue,headers FROM `#{@queues_table}`" do |result|
|
468
531
|
while row = result.fetch_row
|
@@ -481,18 +544,33 @@ module ReliableMsg
|
|
481
544
|
queue << headers unless added
|
482
545
|
end
|
483
546
|
end
|
547
|
+
connection.query "SELECT topic,headers FROM `#{@topics_table}`" do |result|
|
548
|
+
while row = result.fetch_row
|
549
|
+
@topics[row[0]] = Marshal::load row[1]
|
550
|
+
end
|
551
|
+
end
|
484
552
|
end
|
485
553
|
|
486
|
-
|
554
|
+
|
555
|
+
def load id, type, queue_or_topic
|
487
556
|
message = nil
|
488
|
-
|
489
|
-
|
490
|
-
row
|
557
|
+
if type == :queue
|
558
|
+
connection.query "SELECT object FROM `#{@queues_table}` WHERE id='#{id}'" do |result|
|
559
|
+
message = if row = result.fetch_row
|
560
|
+
row[0]
|
561
|
+
end
|
562
|
+
end
|
563
|
+
else
|
564
|
+
connection.query "SELECT object FROM `#{@topics_table}` WHERE topic='#{queue_or_topic}'" do |result|
|
565
|
+
message = if row = result.fetch_row
|
566
|
+
row[0]
|
567
|
+
end
|
491
568
|
end
|
492
569
|
end
|
493
570
|
message
|
494
571
|
end
|
495
572
|
|
573
|
+
|
496
574
|
def connection
|
497
575
|
Thread.current[THREAD_CURRENT_MYSQL] ||= Mysql.new @config[:host], @config[:username], @config[:password],
|
498
576
|
@config[:database], @config[:port], @config[:socket]
|
@@ -501,6 +579,7 @@ module ReliableMsg
|
|
501
579
|
end
|
502
580
|
|
503
581
|
rescue LoadError
|
582
|
+
# do nothing
|
504
583
|
end
|
505
584
|
|
506
585
|
end
|