reliable-msg 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ #
2
+ # = selector.rb - Deferred expression evaluation selector
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
+ # Credit to Jim Weirich who provided the influence. Ideas borrowed from his
10
+ # presentation on domain specific languages, and the BlankSlate source code.
11
+ #
12
+ #--
13
+ # Changes:
14
+ #++
15
+
16
+ module ReliableMsg #:nodoc:
17
+
18
+ class Selector
19
+
20
+ # We're using DRb. Unless we support respond_to? and instance_eval?, DRb will
21
+ # refuse to marshal the selector as an argument and attempt to create a remote
22
+ # object instead.
23
+ instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|respond_to\?|instance_eval$/ }
24
+
25
+ def initialize &block
26
+ # Call the block and hold the deferred value.
27
+ @value = self.instance_eval &block
28
+ end
29
+
30
+ def method_missing symbol, *args
31
+ if symbol == :__evaluate__
32
+ # Evaluate the selector with the headers passed in the argument.
33
+ @value.is_a?(Deferred) ? @value.__evaluate__(*args) : @value
34
+ else
35
+ # Create a deferred value for the missing method (a header).
36
+ raise ArgumentError, "Can't pass arguments to header" unless args.empty?
37
+ Header.new symbol
38
+ end
39
+ end
40
+
41
+
42
+ class Deferred #:nodoc:
43
+
44
+ # We're using DRb. Unless we support respond_to? and instance_eval?, DRb will
45
+ # refuse to marshal the selector as an argument and attempt to create a remote
46
+ # object instead.
47
+ instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|respond_to\?|instance_eval$/ }
48
+
49
+ def initialize target, operation, args
50
+ @target = target
51
+ @operation = operation
52
+ @args = args
53
+ end
54
+
55
+ def coerce value
56
+ [Constant.new(value), self]
57
+ end
58
+
59
+ def method_missing symbol, *args
60
+ if symbol == :__evaluate__
61
+
62
+ eval_args = @args.collect { |arg| arg.instance_of?(Deferred) ? arg.__evaluate__(*args) : arg }
63
+ @target.__evaluate__(*args).send @operation, *eval_args
64
+ else
65
+ Deferred.new self, symbol, args
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ class Header < Deferred #:nodoc:
72
+
73
+ def initialize name
74
+ @name = name
75
+ end
76
+
77
+ def coerce value
78
+ [Constant.new(value), self]
79
+ end
80
+
81
+ def method_missing symbol, *args
82
+ if symbol == :__evaluate__
83
+ args[0][@name]
84
+ else
85
+ Deferred.new self, symbol, args
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ class Constant < Deferred #:nodoc:
92
+
93
+ def initialize value
94
+ @value = value
95
+ end
96
+
97
+ def method_missing symbol, *args
98
+ if symbol == :__evaluate__
99
+ @value
100
+ else
101
+ Deferred.new self, symbol, args
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,384 @@
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