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.
@@ -1,8 +1,8 @@
1
1
  #
2
- # = test-queue.rb - Queue manager test cases
2
+ # = test-queue.rb - Queue API test cases
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
  #
@@ -84,8 +84,12 @@ class TestQueue < Test::Unit::TestCase
84
84
  restart.call if restart
85
85
  msg = @queue.get(ReliableMsg::Queue.selector { name == 'bar' })
86
86
  assert msg && msg.id == id2, "Failed to retrieve message by selector"
87
+ msg = @queue.get(ReliableMsg::Queue.selector { name == 'baz' })
88
+ assert msg.nil?, "Retrieved non-existent message" # Tests reloading selector
87
89
  msg = @queue.get(ReliableMsg::Queue.selector { name == 'foo' })
88
90
  assert msg && msg.id == id1, "Failed to retrieve message by selector"
91
+ msg = @queue.get(ReliableMsg::Queue.selector { name == 'baz' })
92
+ assert msg.nil?, "Retrieved non-existent message" # Tests reloading selector
89
93
  assert @queue.get.nil?, "Phantom message in queue"
90
94
  end
91
95
 
@@ -113,7 +117,7 @@ class TestQueue < Test::Unit::TestCase
113
117
 
114
118
  # Test that we can receive message more than once, but once we try more than
115
119
  # max_deliveries, the message moves to the DLQ.
116
- id1 = @queue.put 'test message', :max_retries=>1, :delivery=>:repeated
120
+ id1 = @queue.put 'test message', :max_deliveries=>2, :delivery=>:repeated
117
121
  restart.call if restart
118
122
  begin
119
123
  @queue.get do |msg|
@@ -136,7 +140,7 @@ class TestQueue < Test::Unit::TestCase
136
140
  assert msg && msg.id == id1, "Message not moved to DLQ"
137
141
 
138
142
  # Test that message discarded when delivery mode is best_effort.
139
- id1 = @queue.put 'test message', :max_retries=>0, :delivery=>:best_effort
143
+ id1 = @queue.put 'test message', :max_deliveries=>1, :delivery=>:best_effort
140
144
  restart.call if restart
141
145
  begin
142
146
  @queue.get do |msg|
@@ -150,7 +154,7 @@ class TestQueue < Test::Unit::TestCase
150
154
  assert @dlq.get.nil?, "Message incorrectly moved to DLQ"
151
155
 
152
156
  # Test that message is moved to DLQ when delivery mode is exactly_once.
153
- id1 = @queue.put 'test message', :max_retries=>2, :delivery=>:once
157
+ id1 = @queue.put 'test message', :max_deliveries=>2, :delivery=>:once
154
158
  restart.call if restart
155
159
  begin
156
160
  @queue.get do |msg|
@@ -166,6 +170,32 @@ class TestQueue < Test::Unit::TestCase
166
170
  assert msg && msg.id == id1, "Message not moved to DLQ"
167
171
  end
168
172
 
173
+
174
+ def test_backout
175
+ @queue.put "backout test", :delivery=>:repeated
176
+ backout = [ 1, 2, 3, 4 ]
177
+ redelivery = nil
178
+ done = false
179
+ while !done
180
+ selector = ReliableMsg::Queue::selector { redelivery.nil? || backout[redelivery - 1] + created <= now }
181
+ begin
182
+ more = @queue.get selector do |msg|
183
+ assert redelivery == msg.headers[:redelivery], "Unexpected redelivery header"
184
+ redelivery = (redelivery || 0) + 1
185
+ raise "Reject message"
186
+ true
187
+ end
188
+ rescue Exception=>error
189
+ assert error.message == "Reject message", "Unexpected ... exception"
190
+ more = true
191
+ end while more
192
+ while msg = @dlq.get
193
+ done = true
194
+ end
195
+ sleep 2
196
+ end
197
+ end
198
+
169
199
  private
170
200
  def clear complain
171
201
  # Empty test queue and DLQ.
@@ -0,0 +1,59 @@
1
+ #
2
+ # = test-rails.rb - Rails integration test cases
3
+ #
4
+ # Author:: Assaf Arkin assaf@labnotes.org
5
+ # Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
6
+ # Copyright:: Copyright (c) 2005 Assaf Arkin
7
+ # License:: MIT and/or Creative Commons Attribution-ShareAlike
8
+ #
9
+ #--
10
+ #++
11
+
12
+ require 'test/unit'
13
+ require 'reliable-msg'
14
+
15
+ class TestRails < Test::Unit::TestCase
16
+
17
+ begin
18
+ class TestController < ActionController::Base
19
+ queue :queue1
20
+ queue :queue2, "queue2"
21
+ queue "queue3"
22
+
23
+ topic :topic1
24
+ topic :topic2, "topic2"
25
+ topic "topic3"
26
+ end
27
+
28
+
29
+ def setup
30
+ @controller = TestController.new
31
+ end
32
+
33
+ def test_queue
34
+ queue = @controller.queue1
35
+ assert queue.instance_of?(ReliableMsg::Queue) && queue.name == "queue1", "Queue1 not set propertly"
36
+ queue = @controller.queue2
37
+ assert queue.instance_of?(ReliableMsg::Queue) && queue.name == "queue2", "Queue2 not set propertly"
38
+ queue = @controller.queue
39
+ assert queue.instance_of?(ReliableMsg::Queue) && queue.name == "queue3", "Queue3 not set propertly"
40
+ end
41
+
42
+ def test_topic
43
+ topic = @controller.topic1
44
+ assert topic.instance_of?(ReliableMsg::Topic) && topic.name == "topic1", "Topic1 not set propertly"
45
+ topic = @controller.topic2
46
+ assert topic.instance_of?(ReliableMsg::Topic) && topic.name == "topic2", "Topic2 not set propertly"
47
+ topic = @controller.topic
48
+ assert topic.instance_of?(ReliableMsg::Topic) && topic.name == "topic3", "Topic3 not set propertly"
49
+ end
50
+ rescue NameError => error
51
+ raise error unless error.name == :ActionController
52
+
53
+ def test_nothing
54
+ puts "ActionController not found: Rails integration test not run"
55
+ end
56
+ end
57
+
58
+ end
59
+
@@ -0,0 +1,102 @@
1
+ #
2
+ # = test-topic.rb - Topic API test cases
3
+ #
4
+ # Author:: Assaf Arkin assaf@labnotes.org
5
+ # Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
6
+ # Copyright:: Copyright (c) 2005 Assaf Arkin
7
+ # License:: MIT and/or Creative Commons Attribution-ShareAlike
8
+ #
9
+ #--
10
+ #++
11
+
12
+ require 'test/unit'
13
+ require 'reliable-msg'
14
+
15
+ class TestTopic < Test::Unit::TestCase
16
+
17
+ class AbortTransaction < Exception
18
+ end
19
+
20
+ def setup
21
+ @topic = ReliableMsg::Topic.new 'test-topic'
22
+ @manager = ReliableMsg::QueueManager.new
23
+ @manager.start
24
+ @restart = proc do
25
+ @manager.stop
26
+ @manager.start
27
+ end
28
+ end
29
+
30
+ def teardown
31
+ @manager.stop
32
+ end
33
+
34
+ def test_single
35
+ # Run test case without restrating queue manager (from cache).
36
+ _test_single
37
+ # Run test case by restarting queue manager (test recovery).
38
+ _test_single @restart
39
+ end
40
+
41
+ def _test_single restart = nil
42
+ # Put one message, check that we can retrieve it. Check that we
43
+ # can only retrieve it once. Then put another message, repeat.
44
+ msg1 = UUID.new
45
+ msg2 = UUID.new
46
+ @topic.put msg1
47
+ restart.call if restart
48
+ msg = @topic.get
49
+ assert msg && msg.object == msg1, "Failed to retrieve last message on topic"
50
+ assert @topic.get.nil?, "Retrieved last message on topic twice"
51
+ @topic.put msg2
52
+ restart.call if restart
53
+ msg = @topic.get
54
+ assert msg && msg.object == msg2, "Failed to retrieve last message on topic"
55
+ assert @topic.get.nil?, "Retrieved last message on topic twice"
56
+ # Test that someone else can retrieve the message.
57
+ new_topic = ReliableMsg::Topic.new @topic.name
58
+ msg = new_topic.get
59
+ assert msg && msg.object == msg2, "Failed to retrieve last message on topic"
60
+ end
61
+
62
+ def test_selector
63
+ # Run test case without restrating queue manager (from cache).
64
+ _test_selector
65
+ # Run test case by restarting queue manager (test recovery).
66
+ _test_selector @restart
67
+ end
68
+
69
+ def _test_selector restart = nil
70
+ msg1 = UUID.new
71
+ @topic.put msg1, :name=>"foo"
72
+ restart.call if restart
73
+ msg = @topic.get(ReliableMsg::Queue.selector { name == 'bar' })
74
+ assert msg.nil?, "Retrieve message with non-matching selector"
75
+ msg = @topic.get(ReliableMsg::Queue.selector { name == 'foo' })
76
+ assert msg && msg.object == msg1, "Failed to retrieve message with selector"
77
+ end
78
+
79
+ def test_non_expires
80
+ # Run test case without restrating queue manager (from cache).
81
+ _test_non_expires
82
+ # Run test case by restarting queue manager (test recovery).
83
+ _test_non_expires @restart
84
+ end
85
+
86
+ def _test_non_expires restart = nil
87
+ # Test that we can receive message that has not yet expired (30 second delay),
88
+ # but cannot receive message that has expires (1 second, we wait for 2).
89
+ msg1 = UUID.new
90
+ msg2 = UUID.new
91
+ @topic.put msg1, :expires=>30
92
+ restart.call if restart
93
+ msg = @topic.get
94
+ assert msg && msg.object == msg1, "Failed to retrieve message"
95
+ @topic.put msg2, :expires=>1
96
+ restart.call if restart
97
+ sleep 2
98
+ msg = @topic.get
99
+ assert msg.nil?, "Incorrectly retrieved expired message"
100
+ end
101
+ end
102
+
metadata CHANGED
@@ -1,63 +1,76 @@
1
- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.3
3
3
  specification_version: 1
4
4
  name: reliable-msg
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.1
7
- date: 2005-11-11 00:00:00 -08:00
6
+ version: 1.1.0
7
+ date: 2005-11-26
8
8
  summary: Reliable messaging and persistent queues for building asynchronous applications in Ruby
9
9
  require_paths:
10
- - lib
10
+ - lib
11
11
  email: assaf@labnotes.org
12
- homepage: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/RubyReliableMessaging
13
- rubyforge_project:
14
- description: This package provides reliable messaging and persistent queues for building asynchronous applications in Ruby. It supports transaction processing, message selectors, priorities, delivery semantics, remote queue managers, disk-based and MySQL message stores and more.
12
+ homepage: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
13
+ rubyforge_project: reliable-msg
14
+ description: "This package provides reliable messaging and persistent queues for building
15
+ asynchronous applications in Ruby. It supports transaction processing, message
16
+ selectors, priorities, delivery semantics, remote queue managers, disk-based and
17
+ MySQL message stores and more."
15
18
  autorequire: reliable-msg.rb
16
19
  default_executable: queues
17
20
  bindir: bin
18
21
  has_rdoc: true
19
22
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
23
  requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
+ -
25
+ - ">"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.0.0
24
28
  version:
25
29
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
30
  authors:
29
- - Assaf Arkin
31
+ - Assaf Arkin
30
32
  files:
31
- - bin/queues
32
- - test/test-queue.rb
33
- - test/test-uuid.rb
34
- - lib/reliable-msg
35
- - lib/reliable-msg.rb
36
- - lib/uuid.rb
37
- - lib/reliable-msg/cli.rb
38
- - lib/reliable-msg/message-store.rb
39
- - lib/reliable-msg/mysql.sql
40
- - lib/reliable-msg/queue-manager.rb
41
- - lib/reliable-msg/queue.rb
42
- - lib/reliable-msg/selector.rb
43
- - README
44
- - MIT-LICENSE
45
- - Rakefile
33
+ - bin/queues
34
+ - test/test-queue.rb
35
+ - test/test-rails.rb
36
+ - test/test-topic.rb
37
+ - lib/reliable-msg
38
+ - lib/reliable-msg.rb
39
+ - lib/reliable-msg/cli.rb
40
+ - lib/reliable-msg/client.rb
41
+ - lib/reliable-msg/message-store.rb
42
+ - lib/reliable-msg/mysql.sql
43
+ - lib/reliable-msg/queue-manager.rb
44
+ - lib/reliable-msg/queue.rb
45
+ - lib/reliable-msg/rails.rb
46
+ - lib/reliable-msg/selector.rb
47
+ - lib/reliable-msg/topic.rb
48
+ - README
49
+ - MIT-LICENSE
50
+ - Rakefile
51
+ - changelog.txt
46
52
  test_files: []
47
-
48
53
  rdoc_options:
49
- - --main
50
- - README
51
- - --title
52
- - Reliable Messaging for Ruby
53
- - --line-numbers
54
+ - "--main"
55
+ - README
56
+ - "--title"
57
+ - Reliable Messaging for Ruby
58
+ - "--line-numbers"
54
59
  extra_rdoc_files:
55
- - README
60
+ - README
56
61
  executables:
57
- - queues
62
+ - queues
58
63
  extensions: []
59
-
60
64
  requirements:
61
- - MySQL for database store, otherwise uses the file system
62
- dependencies: []
63
-
65
+ - "MySQL for database store, otherwise uses the file system"
66
+ dependencies:
67
+ - !ruby/object:Gem::Dependency
68
+ name: uuid
69
+ version_requirement:
70
+ version_requirements: !ruby/object:Gem::Version::Requirement
71
+ requirements:
72
+ -
73
+ - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ version:
@@ -1,384 +0,0 @@
1
- #
2
- # = uuid.rb - UUID generator
3
- #
4
- # Author:: Assaf Arkin assaf@labnotes.org
5
- # Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/RubyReliableMessaging
6
- # Copyright:: Copyright (c) 2005 Assaf Arkin
7
- # License:: MIT and/or Creative Commons Attribution-ShareAlike
8
- #
9
- #--
10
- # Changes:
11
- # 11/07/05 -- Incorporated into reliable-msg package
12
- #++
13
-
14
-
15
- require 'thread'
16
- require 'yaml'
17
- require 'singleton'
18
- require 'logger'
19
-
20
-
21
-
22
- # == Generating UUIDs
23
- #
24
- # Call UUID.new to generate and return a new UUID. The method returns a string in one of three
25
- # formats. The default format is 36 characters long, and contains the 32 hexadecimal octets and
26
- # hyphens separating the various value parts. The <tt>:compact</tt> format omits the hyphens,
27
- # while the <tt>:urn</tt> format adds the <tt>:urn:uuid</tt> prefix.
28
- #
29
- # For example:
30
- # 10.times do
31
- # p UUID.new
32
- # end
33
- #
34
- # ---
35
- # == UUIDs in Brief
36
- #
37
- # UUID (universally unique identifier) are guaranteed to be unique across time and space.
38
- #
39
- # A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and
40
- # a 48-bit node identifier.
41
- #
42
- # The time value is taken from the system clock, and is monotonically incrementing. However,
43
- # since it is possible to set the system clock backward, a sequence number is added. The
44
- # sequence number is incremented each time the UUID generator is started. The combination
45
- # guarantees that identifiers created on the same machine are unique with a high degree of
46
- # probability.
47
- #
48
- # Note that due to the structure of the UUID and the use of sequence number, there is no
49
- # guarantee that UUID values themselves are monotonically incrementing. The UUID value
50
- # cannot itself be used to sort based on order of creation.
51
- #
52
- # To guarantee that UUIDs are unique across all machines in the network, use the IEEE 802
53
- # MAC address of the machine's network interface card as the node identifier. Network interface
54
- # cards have unique MAC address that are 47-bit long (the last bit acts as a broadcast flag).
55
- # Use +ipconfig+ (Windows), or +ifconfig+ (Unix) to find the MAC address (aka physical address)
56
- # of a network card. It takes the form of six pairs of hexadecimal digits, separated by hypen or
57
- # colon, e.g. '<tt>08-0E-46-21-4B-35</tt>'
58
- #
59
- # For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
60
- #
61
- # == Configuring the UUID generator
62
- #
63
- # The UUID generator requires a state file which maintains the MAC address and next sequence
64
- # number to use. By default, the UUID generator will use the file <tt>uuid.state</tt> contained
65
- # in the current directory, or in the installation directory.
66
- #
67
- # Use UUID.config to specify a different location for the UUID state file. If the UUID state file
68
- # does not exist, you can create one manually, or use UUID.config with the options <tt>:sequence</tt>
69
- # and <tt>:mac_addr</tt>.
70
- #
71
- # A UUID state file looks like:
72
- # ---
73
- # last_clock: "0x28227f76122d80"
74
- # mac_addr: 08-0E-46-21-4B-35
75
- # sequence: "0x1639"
76
- #
77
- #
78
- #--
79
- # === Time-based UUID
80
- #
81
- # The UUID specification prescribes the following format for representing UUIDs. Four octets encode
82
- # the low field of the time stamp, two octects encode the middle field of the timestamp, and two
83
- # octets encode the high field of the timestamp with the version number. Two octets encode the
84
- # clock sequence number and six octets encode the unique node identifier.
85
- #
86
- # The timestamp is a 60 bit value holding UTC time as a count of 100 nanosecond intervals since
87
- # October 15, 1582. UUIDs generated in this manner are guaranteed not to roll over until 3400 AD.
88
- #
89
- # The clock sequence is used to help avoid duplicates that could arise when the clock is set backward
90
- # in time or if the node ID changes. Although the system clock is guaranteed to be monotonic, the
91
- # system clock is not guaranteed to be monotonic across system failures. The UUID cannot be sure
92
- # that no UUIDs were generated with timestamps larger than the current timestamp.
93
- #
94
- # If the clock sequence can be determined at initialization, it is incremented by one. The clock sequence
95
- # MUST be originally (i.e. once in the lifetime of a system) initialized to a random number to minimize the
96
- # correlation across systems. The initial value must not be correlated to the node identifier.
97
- #
98
- # The node identifier must be unique for each UUID generator. This is accomplished using the IEEE 802
99
- # network card address. For systems with multiple IEEE 802 addresses, any available address can be used.
100
- # For systems with no IEEE address, a 47 bit random value is used and the multicast bit is set so it will
101
- # never conflict with addresses obtained from network cards.
102
- #
103
- # === UUID state file
104
- #
105
- # The UUID state is contained in the UUID state file. The file name can be specified when configuring
106
- # the UUID generator with UUID.config. The default is to use the file +uuid.state+ in the current directory,
107
- # or the installation directory.
108
- #
109
- # The UUID state file is read once when the UUID generator is first used (or configured). The sequence
110
- # number contained in the UUID is read and used, and the state file is updated to the next sequence
111
- # number. The MAC address is also read from the state file. The current clock time (in 100ns resolution)
112
- # is stored in the state file whenever the sequence number is updated, but is never read.
113
- #
114
- # If the UUID generator detects that the system clock has been moved backwards, it will obtain a new
115
- # sequence in the same manner. So it is possible that the UUID state file will be updated while the
116
- # application is running.
117
- #++
118
- module UUID
119
-
120
- PACKAGE = "uuid" #:nodoc:
121
-
122
- # Default state file.
123
- STATE_FILE = "uuid.state"
124
-
125
- # Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
126
- CLOCK_MULTIPLIER = 10000000 #:nodoc:
127
-
128
- # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
129
- CLOCK_GAPS = 100000 #:nodoc:
130
-
131
- # Version number stamped into the UUID to identify it as time-based.
132
- VERSION_CLOCK = 0x0100 #:nodoc:
133
-
134
- # Formats supported by the UUID generator.
135
- FORMATS = {:compact=>"%08x%04x%04x%04x%012x", :default=>"%08x-%04x-%04x-%04x-%012x", :urn=>"urn:uuid:%08x-%04x-%04x-%04x-%012x"} #:nodoc:
136
-
137
- # Length (in characters) of UUIDs generated for each of the formats.
138
- FORMATS_LENGTHS = {:compact=>32, :default=>36, :urn=>45} #:nodoc:
139
-
140
- ERROR_INVALID_SEQUENCE = "Invalid sequence number: found '%s', expected 4 hexdecimal digits" #:nodoc:
141
-
142
- ERROR_NOT_A_SEQUENCE = "Not a sequence number: expected integer between 0 and 0xFFFF" #:nodoc:
143
-
144
- ERROR_INVALID_MAC_ADDR = "Invalid MAC address: found '%s', expected a number in the format XX-XX-XX-XX-XX-XX" #:nodoc:
145
-
146
- INFO_INITIALIZED = "Initialized UUID generator with sequence number 0x%04x and MAC address %s" #:nodoc:
147
-
148
- ERROR_INITIALIZED_RANDOM_1 = "Initialized UUID generator with random sequence number/MAC address." #:nodoc:
149
-
150
- ERROR_INITIALIZED_RANDOM_2 = "UUIDs are not guaranteed to be unique. Please create a uuid.state file as soon as possible." #:nodoc:
151
-
152
- IFCONFIG_PATTERN = /[^:\-](?:[0-9A-Za-z][0-9A-Za-z][:\-]){5}[0-9A-Za-z][0-9A-Za-z][^:\-]/ #:nodoc:
153
-
154
- @@mutex = Mutex.new
155
- @@last_clock = @@logger = @@state_file = nil
156
-
157
- # Generates and returns a new UUID string.
158
- #
159
- # The argument +format+ specifies which formatting template to use:
160
- # * <tt>:default</tt> -- Produces 36 characters, including hyphens separating the UUID value parts
161
- # * <tt>:compact</tt> -- Produces a 32 digits (hexadecimal) value with no hyphens
162
- # * <tt>:urn</tt> -- Aadds the prefix <tt>urn:uuid:</tt> to the <tt>:default</tt> format
163
- #
164
- # For example:
165
- # print UUID.new :default
166
- # or just
167
- # print UUID.new
168
- #
169
- # :call-seq:
170
- # UUID.new([format]) -> string
171
- #
172
- def new format = nil
173
- # Determine which format we're using for the UUID string.
174
- template = FORMATS[format || :default] or
175
- raise RuntimeError, "I don't know the format '#{format}'"
176
-
177
- # The clock must be monotonically increasing. The clock resolution is at best 100 ns
178
- # (UUID spec), but practically may be lower (on my setup, around 1ms). If this method
179
- # is called too fast, we don't have a monotonically increasing clock, so the solution is
180
- # to just wait.
181
- # It is possible for the clock to be adjusted backwards, in which case we would end up
182
- # blocking for a long time. When backward clock is detected, we prevent duplicates by
183
- # asking for a new sequence number and continue with the new clock.
184
- clock = @@mutex.synchronize do
185
- # Initialize UUID generator if not already initialized. Uninitizlied UUID generator has no
186
- # last known clock.
187
- next_sequence unless @@last_clock
188
- clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
189
- if clock > @@last_clock
190
- @@drift = 0
191
- @@last_clock = clock
192
- elsif clock = @@last_clock
193
- drift = @@drift += 1
194
- if drift < 10000
195
- @@last_clock += 1
196
- else
197
- Thread.pass
198
- nil
199
- end
200
- else
201
- next_sequence
202
- @@last_clock = clock
203
- end
204
- end while not clock
205
- sprintf template, clock & 0xFFFFFFFF, (clock >> 32)& 0xFFFF, ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
206
- @@sequence & 0xFFFF, @@mac_hex & 0xFFFFFFFFFFFF
207
- end
208
-
209
- alias uuid new
210
- module_function :uuid, :new
211
-
212
- # Configures the UUID generator. Use this method to specify the UUID state file, logger, etc.
213
- #
214
- # The method accepts the following options:
215
- # * <tt>:state_file</tt> -- Specifies the location of the state file. If missing, the default
216
- # is <tt>uuid.state</tt>
217
- # * <tt>:logger<tt> -- The UUID generator will use this logger to report the state information (optional).
218
- # * <tt>:sequence</tt> -- Specifies the sequence number (0 to 0xFFFF) to use. Required to
219
- # create a new state file, ignored if the state file already exists.
220
- # * <tt>:mac_addr</tt> -- Specifies the MAC address (xx-xx-xx-xx-xx) to use. Required to
221
- # create a new state file, ignored if the state file already exists.
222
- #
223
- # For example, to create a new state file:
224
- # UUID.config :state_file=>'my-uuid.state', :sequence=>rand(0x10000), :mac_addr=>'0C-0E-35-41-60-65'
225
- # To use an existing state file and log to +STDOUT+:
226
- # UUID.config :state_file=>'my-uuid.state', :logger=>Logger.new(STDOUT)
227
- #
228
- # :call-seq:
229
- # UUID.config(config)
230
- #
231
- def self.config options
232
- options ||= {}
233
- @@mutex.synchronize do
234
- @@logger = options[:logger]
235
- next_sequence options
236
- end
237
- end
238
-
239
- # Create a uuid.state file by finding the IEEE 802 NIC MAC address for this machine.
240
- # Works for UNIX (ifconfig) and Windows (ipconfig). Creates the uuid.state file in the
241
- # installation directory (typically the GEM's lib).
242
- def self.setup
243
- file = File.expand_path(File.dirname(__FILE__))
244
- file = File.basename(file) == 'lib' ? file = File.join(file, '..', STATE_FILE) : file = File.join(file, STATE_FILE)
245
- file = File.expand_path(file)
246
- if File.exist? file
247
- puts "#{PACKAGE}: Found an existing UUID state file: #{file}"
248
- else
249
- puts "#{PACKAGE}: No UUID state file found, attempting to create one for you:"
250
- # Run ifconfig for UNIX, or ipconfig for Windows.
251
- config = ""
252
- begin
253
- Kernel.open "|ifconfig" do |input|
254
- input.each_line { |line| config << line }
255
- end
256
- rescue
257
- end
258
- begin
259
- Kernel.open "|ipconfig /all" do |input|
260
- input.each_line { |line| config << line }
261
- end
262
- rescue
263
- end
264
-
265
- addresses = config.scan(IFCONFIG_PATTERN).collect { |addr| addr[1..-2] }
266
- if addresses.empty?
267
- puts "Could not find any IEEE 802 NIC MAC addresses for this machine."
268
- puts "You need to create the uuid.state file manually."
269
- else
270
- puts "Found the following IEEE 802 NIC MAC addresses on your computer:"
271
- addresses.each { |addr| puts " #{addr}" }
272
- puts "Selecting the first address #{addresses[0]} for use in your UUID state file."
273
- File.open file, "w" do |output|
274
- output.puts "mac_addr: #{addresses[0]}"
275
- output.puts format("sequence: \"0x%04x\"", rand(0x10000))
276
- end
277
- puts "Created a new UUID state file: #{file}"
278
- end
279
- end
280
- file
281
- end
282
-
283
- private
284
- def self.state plus_one = false
285
- return nil unless @@sequence && @@mac_addr
286
- {"sequence"=>sprintf("0x%04x", (plus_one ? @@sequence + 1 : @@sequence) & 0xFFFF), "last_clock"=>sprintf("0x%x", @@last_clock || (Time.new.to_f * CLOCK_MULTIPLIER).to_i), "mac_addr" => @@mac_addr}
287
- end
288
-
289
- def self.next_sequence config = nil
290
- # If called to advance the sequence number (config is nil), we have a state file that we're able to use.
291
- # If called from configuration, use the specified or default state file.
292
- state_file = (config && config[:state_file]) || @@state_file
293
- unless state_file
294
- state_file = if File.exist?(STATE_FILE)
295
- STATE_FILE
296
- else
297
- file = File.expand_path(File.dirname(__FILE__))
298
- file = File.basename(file) == 'lib' ? file = File.join(file, '..', STATE_FILE) : file = File.join(file, STATE_FILE)
299
- setup unless File.exist?(file)
300
- end
301
- end
302
- begin
303
- File.open state_file, "r+" do |file|
304
- # Lock the file for exclusive access, just to make sure it's not being read while we're
305
- # updating its contents.
306
- file.flock(File::LOCK_EX)
307
- state = YAML::load file
308
- # Get the sequence number. Must be a valid 16-bit hexadecimal value.
309
- sequence = state['sequence']
310
- if sequence
311
- raise RuntimeError, format(ERROR_INVALID_SEQUENCE, sequence) unless sequence.is_a?(String) and sequence =~ /[0-9a-fA-F]{4}/
312
- sequence = sequence.hex & 0xFFFF
313
- else
314
- sequence = rand(0x10000)
315
- end
316
- # Get the MAC address. Must be 6 pairs of hexadecimal octets. Convert MAC address into
317
- # a 48-bit value with the higher bit being zero.
318
- mac_addr = state['mac_addr']
319
- raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless mac_addr.is_a?(String) and mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
320
- mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
321
-
322
- # If everything is OK, proceed to the next step. Grab the sequence number and store
323
- # the new state. Start at beginning of file, and truncate file when done.
324
- @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
325
- file.pos = 0
326
- YAML::dump state(true), file
327
- file.truncate file.pos
328
- end
329
- # Initialized.
330
- if @@logger
331
- @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
332
- else
333
- warn "#{PACKAGE}: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
334
- end
335
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
336
- rescue Errno::ENOENT=>error
337
- if !config
338
- # Generate random values.
339
- @@mac_hex, @@sequence, @@state_file = rand(0x800000000000) | 0xF00000000000, rand(0x10000), nil
340
- # Initialized.
341
- if @@logger
342
- @@logger.error ERROR_INITIALIZED_RANDOM_1
343
- @@logger.error ERROR_INITIALIZED_RANDOM_2
344
- else
345
- warn "#{PACKAGE}: " + ERROR_INITIALIZED_RANDOM_1
346
- warn "#{PACKAGE}: " + ERROR_INITIALIZED_RANDOM_2
347
- end
348
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
349
- else
350
- # No state file. If we were called for configuration with valid sequence number and MAC address,
351
- # attempt to create state file. See code above for how we interpret these values.
352
- sequence = config[:sequence]
353
- raise RuntimeError, format(ERROR_NOT_A_SEQUENCE, sequence) unless sequence.is_a?(Integer)
354
- sequence &= 0xFFFF
355
- mac_addr = config[:mac_addr]
356
- raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless mac_addr.is_a?(String) and mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
357
- mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
358
- File.open state_file, "w" do |file|
359
- file.flock(File::LOCK_EX)
360
- @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
361
- file.pos = 0
362
- YAML::dump state(true), file
363
- file.truncate file.pos
364
- end
365
- # Initialized.
366
- if @@logger
367
- @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
368
- else
369
- warn "#{PACKAGE}: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
370
- end
371
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
372
- end
373
- rescue Exception=>error
374
- @@last_clock = nil
375
- raise error
376
- end
377
- end
378
-
379
- end
380
-
381
-
382
- if __FILE__ == $0
383
- UUID.setup
384
- end