reliable-msg 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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