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.
@@ -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/RubyReliableMessaging
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 'uuid'
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
- def select queue, &block
102
- queue = @queues[queue]
103
- return nil unless queue
104
- queue.each do |headers|
105
- selected = block.call(headers)
106
- if selected
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
- queue = @queues[insert[:queue]] ||= []
134
- headers = insert[:headers]
135
- # Add element based on priority, higher priority comes first.
136
- priority = headers[:priority]
137
- added = false
138
- queue.each_index do |idx|
139
- if queue[idx][:priority] < priority
140
- queue[idx, 0] = headers
141
- added = true
142
- break
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
- queue = @queues[delete[:queue]]
150
- id = delete[:id]
151
- queue.delete_if { |headers| headers[:id] == id }
152
- @cache.delete id
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, "The path '#{@path}' is not a directory" unless File.directory?(@path)
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, "The path '#{@path}' is not a directory" unless File.directory?(@path)
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, "Cannot write to master index file '#{index}'" unless File.writable?(index)
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.truncate file.pos
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 and
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
- def load id, queue
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
- exists = false
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
- exists = true if row[0] == @queues_table
457
+ requires -= 1 if row[0] == @queues_table || row[0] == @topics_table
404
458
  end
405
459
  end
406
- if exists
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
- 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]}')"
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
- def load id, queue
554
+
555
+ def load id, type, queue_or_topic
487
556
  message = nil
488
- connection.query "SELECT object FROM `#{@queues_table}` WHERE id='#{id}'" do |result|
489
- message = if row = result.fetch_row
490
- row[0]
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