madeleine 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,418 @@
1
+ require 'yaml'
2
+ require 'madeleine/zmarshal'
3
+ require 'soap/marshal'
4
+
5
+ module Madeleine
6
+
7
+ # Automatic commands for Madeleine
8
+ #
9
+ # Author:: Stephen Sykes <sds@stephensykes.com>
10
+ # Copyright:: Copyright (C) 2003-2004
11
+ # Version:: 0.41
12
+ #
13
+ # This module provides a way of automatically generating command objects for madeleine to
14
+ # store. It works by making a proxy object for all objects of any classes in which it is included.
15
+ # Method calls to these objects are intercepted, and stored as a command before being
16
+ # passed on to the real receiver. The command objects remember which object the command was
17
+ # destined for by using a pair of internal ids that are contained in each of the proxy objects.
18
+ #
19
+ # There is also a mechanism for specifying which methods not to intercept calls to by using
20
+ # automatic_read_only, and its opposite automatic_read_write.
21
+ #
22
+ # Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass
23
+ # the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new.
24
+ # If the passed marshaller did not successfully deserialize the latest snapshot, the system
25
+ # will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding
26
+ # compressed versions.
27
+ #
28
+ # This module is designed to work correctly in the case there are multiple madeleine systems in use by
29
+ # a single program, and is also safe to use with threads.
30
+ #
31
+ # Usage:
32
+ #
33
+ # require 'madeleine'
34
+ # require 'madeleine/automatic'
35
+ #
36
+ # class A
37
+ # include Madeleine::Automatic::Interceptor
38
+ # attr_reader :foo
39
+ # automatic_read_only :foo
40
+ # def initialize(param1, ...)
41
+ # ...
42
+ # end
43
+ # def some_method(paramA, ...)
44
+ # ...
45
+ # end
46
+ # automatic_read_only
47
+ # def bigfoo
48
+ # foo.upcase
49
+ # end
50
+ # end
51
+ #
52
+ # mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
53
+ #
54
+ # mad.system.some_method(paramA, ...) # logged as a command by madeleine
55
+ # print mad.foo # not logged
56
+ # print mad.bigfoo # not logged
57
+ # mad.take_snapshot
58
+ #
59
+
60
+ module Automatic
61
+ #
62
+ # This module should be included (at the top) in any classes that are to be persisted.
63
+ # It will intercept method calls and make sure they are converted into commands that are logged by Madeleine.
64
+ # It does this by returning a Prox object that is a proxy for the real object.
65
+ #
66
+ # It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods
67
+ # should be made into commands
68
+ #
69
+ module Interceptor
70
+ #
71
+ # When included, redefine new so that we can return a Prox object instead, and define methods to handle
72
+ # keeping track of which methods are read only
73
+ #
74
+ def self.included(klass)
75
+ class <<klass
76
+ alias_method :_old_new, :new
77
+
78
+ def new(*args, &block)
79
+ Prox.new(_old_new(*args, &block))
80
+ end
81
+ #
82
+ # Called when a method added - remember symbol if read only
83
+ # This is a good place to add in any superclass's read only methods also
84
+ #
85
+ def method_added(symbol)
86
+ self.instance_eval {
87
+ @read_only_methods ||= []
88
+ @auto_read_only_flag ||= false
89
+ @read_only_methods << symbol if @auto_read_only_flag
90
+ c = self
91
+ while (c = c.superclass)
92
+ if (c.instance_eval {instance_variables.include? "@read_only_methods"})
93
+ @read_only_methods |= c.instance_eval {@read_only_methods}
94
+ end
95
+ end
96
+ }
97
+ end
98
+ #
99
+ # Set the read only flag, or add read only methods
100
+ #
101
+ def automatic_read_only(*list)
102
+ if (list == [])
103
+ self.instance_eval {@auto_read_only_flag = true}
104
+ else
105
+ list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods << s}}
106
+ end
107
+ end
108
+ #
109
+ # Clear the read only flag, or remove read only methods
110
+ #
111
+ def automatic_read_write(*list)
112
+ if (list == [])
113
+ self.instance_eval {@auto_read_only_flag = false}
114
+ else
115
+ list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods.delete(s)}}
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+ #
122
+ # Return the list of read only methods so Automatic_proxy#method_missing can find what to and what not to make into a command
123
+ #
124
+ def read_only_methods
125
+ self.class.instance_eval {@read_only_methods}
126
+ end
127
+ end
128
+
129
+ #
130
+ # A Command object is automatically created for each method call to an object within the system that comes from without.
131
+ # These objects are recorded in the log by Madeleine.
132
+ #
133
+ class Command
134
+ def initialize(symbol, myid, *args)
135
+ @symbol = symbol
136
+ @myid = myid
137
+ @args = args
138
+ end
139
+ #
140
+ # Called by madeleine when the command is done either first time, or when restoring the log
141
+ #
142
+ def execute(system)
143
+ Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args)
144
+ end
145
+ end
146
+ #
147
+ # This is a little class to pass to SnapshotMadeleine. This is used for snapshots only.
148
+ # It acts as the marshaller, and just passes marshalling requests on to the user specified
149
+ # marshaller. This defaults to Marshal, but could be YAML or another.
150
+ # After we have done a restore, the ObjectSpace is searched for instances of Prox to
151
+ # add new objects to the list in AutomaticSnapshotMadeleine
152
+ #
153
+ class Automatic_marshaller #:nodoc:
154
+ def Automatic_marshaller.load(io)
155
+ restored_obj = Deserialize.load(io, Thread.current[:system].marshaller)
156
+ ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)}
157
+ restored_obj
158
+ end
159
+ def Automatic_marshaller.dump(obj, io = nil)
160
+ Thread.current[:system].marshaller.dump(obj, io)
161
+ end
162
+ end
163
+ #
164
+ # A Prox object is generated and returned by Interceptor each time a system object is created.
165
+ #
166
+ class Prox #:nodoc:
167
+ attr_accessor :thing, :myid, :sysid
168
+
169
+ def initialize(thing)
170
+ if (thing)
171
+ raise "App object created outside of app" unless Thread.current[:system]
172
+ @sysid = Thread.current[:system].sysid
173
+ @myid = Thread.current[:system].add(self)
174
+ @thing = thing
175
+ end
176
+ end
177
+ #
178
+ # This automatically makes and executes a new Command if a method is called from
179
+ # outside the system.
180
+ #
181
+ def method_missing(symbol, *args, &block)
182
+ # print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n"
183
+ raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol)
184
+ if (Thread.current[:system])
185
+ @thing.send(symbol, *args, &block)
186
+ else
187
+ raise "Cannot make command with block" if block_given?
188
+ Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid]
189
+ begin
190
+ if (@thing.read_only_methods.include?(symbol))
191
+ result = Thread.current[:system].execute_query(Command.new(symbol, @myid, *args))
192
+ else
193
+ result = Thread.current[:system].execute_command(Command.new(symbol, @myid, *args))
194
+ end
195
+ ensure
196
+ Thread.current[:system] = false
197
+ end
198
+ result
199
+ end
200
+ end
201
+ #
202
+ # Custom marshalling - this adds the internal id (myid) and the system id to a marshal
203
+ # of the object we are the proxy for.
204
+ # We take care to not marshal the same object twice, so circular references will work.
205
+ # We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and
206
+ # marshal is always used for Command objects
207
+ #
208
+ def _dump(depth)
209
+ if (Thread.current[:snapshot_memory])
210
+ if (Thread.current[:snapshot_memory][self])
211
+ [@myid.to_s, @sysid].pack("A8A30")
212
+ else
213
+ Thread.current[:snapshot_memory][self] = true
214
+ [@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth)
215
+ end
216
+ else
217
+ [@myid.to_s, @sysid].pack("A8A30") # never marshal a prox object in a command, just ref
218
+ end
219
+ end
220
+
221
+ #
222
+ # Custom marshalling for Marshal - restore a Prox object.
223
+ #
224
+ def Prox._load(str)
225
+ x = Prox.new(nil)
226
+ a = str.unpack("A8A30a*")
227
+ x.myid = a[0].to_i
228
+ x.sysid = a[1]
229
+ x = Thread.current[:system].restore(x)
230
+ x.thing = Marshal.load(a[2]) if (a[2] > "")
231
+ x
232
+ end
233
+
234
+ end
235
+
236
+ #
237
+ # The AutomaticSnapshotMadeleine class contains an instance of the persister
238
+ # (default is SnapshotMadeleine) and provides additional automatic functionality.
239
+ #
240
+ # The class is instantiated the same way as SnapshotMadeleine:
241
+ # madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
242
+ # The second initialisation parameter is the persister. Supported persisters are:
243
+ #
244
+ # * Marshal (default)
245
+ # * YAML
246
+ # * SOAP::Marshal
247
+ # * Madeleine::ZMarshal.new(Marshal)
248
+ # * Madeleine::ZMarshal.new(YAML)
249
+ # * Madeleine::ZMarshal.new(SOAP::Marshal)
250
+ #
251
+ # The class keeps a record of all the systems that currently exist.
252
+ # Each instance of the class keeps a record of Prox objects in that system by internal id (myid).
253
+ #
254
+ # We also add functionality to take_snapshot in order to set things up so that the custom Prox object
255
+ # marshalling will work correctly.
256
+ #
257
+ class AutomaticSnapshotMadeleine
258
+ attr_accessor :marshaller
259
+ attr_reader :list, :sysid
260
+
261
+ def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block)
262
+ @sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid
263
+ @myid_count = 0
264
+ @list = {}
265
+ Thread.current[:system] = self # during system startup system should not create commands
266
+ Thread.critical = true
267
+ @@systems ||= {} # holds systems by sysid
268
+ @@systems[@sysid] = self
269
+ Thread.critical = false
270
+ @marshaller = marshaller # until attrb
271
+ begin
272
+ @persister = persister.new(directory_name, Automatic_marshaller, &new_system_block)
273
+ @list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid
274
+ begin
275
+ ObjectSpace._id2ref(v).sysid = @sysid
276
+ false
277
+ rescue RangeError
278
+ true # Id was to a GC'd object, delete it
279
+ end
280
+ }
281
+ ensure
282
+ Thread.current[:system] = false
283
+ end
284
+ end
285
+ #
286
+ # Add a proxy object to the list, return the myid for that object
287
+ #
288
+ def add(proxo)
289
+ @list[@myid_count += 1] = proxo.object_id
290
+ @myid_count
291
+ end
292
+ #
293
+ # Restore a marshalled proxy object to list - myid_count is increased as required.
294
+ # If the object already exists in the system then the existing object must be used.
295
+ #
296
+ def restore(proxo)
297
+ if (@list[proxo.myid])
298
+ proxo = myid2ref(proxo.myid)
299
+ else
300
+ @list[proxo.myid] = proxo.object_id
301
+ @myid_count = proxo.myid if (@myid_count < proxo.myid)
302
+ end
303
+ proxo
304
+ end
305
+ #
306
+ # Returns a reference to the object indicated by the internal id supplied.
307
+ #
308
+ def myid2ref(myid)
309
+ raise "Internal id #{myid} not found" unless objid = @list[myid]
310
+ ObjectSpace._id2ref(objid)
311
+ end
312
+ #
313
+ # Take a snapshot of the system.
314
+ #
315
+ def take_snapshot
316
+ begin
317
+ Thread.current[:system] = self
318
+ Thread.current[:snapshot_memory] = {}
319
+ @persister.take_snapshot
320
+ ensure
321
+ Thread.current[:snapshot_memory] = nil
322
+ Thread.current[:system] = false
323
+ end
324
+ end
325
+ #
326
+ # Returns the hash containing the systems.
327
+ #
328
+ def AutomaticSnapshotMadeleine.systems
329
+ @@systems
330
+ end
331
+ #
332
+ # Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new
333
+ # system before GC gets them
334
+ #
335
+ def close
336
+ begin
337
+ @list.each_key {|k| myid2ref(k).sysid = nil}
338
+ rescue RangeError
339
+ # do nothing
340
+ end
341
+ @persister.close
342
+ end
343
+
344
+ #
345
+ # Pass on any other calls to the persister
346
+ #
347
+ def method_missing(symbol, *args, &block)
348
+ @persister.send(symbol, *args, &block)
349
+ end
350
+ end
351
+
352
+
353
+ module Deserialize #:nodoc:
354
+ #
355
+ # Detect format of an io stream. Leave it rewound.
356
+ #
357
+ def Deserialize.detect(io)
358
+ c = io.getc
359
+ c1 = io.getc
360
+ io.rewind
361
+ if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION)
362
+ Marshal
363
+ elsif (c == 31 && c1 == 139) # gzip magic numbers
364
+ ZMarshal
365
+ else
366
+ while (s = io.gets)
367
+ break if (s !~ /^\s*$/) # ignore blank lines
368
+ end
369
+ io.rewind
370
+ if (s && s =~ /^\s*<\?[xX][mM][lL]/) # "<?xml" begins an xml serialization
371
+ SOAP::Marshal
372
+ else
373
+ while (s = io.gets)
374
+ break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines
375
+ end
376
+ io.rewind
377
+ if (s && s =~ /^\s*---/) # "---" is the yaml header
378
+ YAML
379
+ else
380
+ nil # failed to detect
381
+ end
382
+ end
383
+ end
384
+ end
385
+ #
386
+ # Try to deserialize object. If there was an error, try to detect marshal format,
387
+ # and return deserialized object using the right marshaller
388
+ # If detection didn't work, raise up the exception
389
+ #
390
+ def Deserialize.load(io, marshaller=Marshal)
391
+ begin
392
+ marshaller.load(io)
393
+ rescue Exception => e
394
+ io.rewind
395
+ detected_marshaller = detect(io)
396
+ if (detected_marshaller == ZMarshal)
397
+ zio = Zlib::GzipReader.new(io)
398
+ detected_zmarshaller = detect(zio)
399
+ zio.finish
400
+ io.rewind
401
+ if (detected_zmarshaller)
402
+ ZMarshal.new(detected_zmarshaller).load(io)
403
+ else
404
+ raise e
405
+ end
406
+ elsif (detected_marshaller)
407
+ detected_marshaller.load(io)
408
+ else
409
+ raise e
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ end
416
+ end
417
+
418
+ AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine
@@ -0,0 +1,94 @@
1
+ #
2
+ # Copyright(c) Anders Bengtsson 2003
3
+ #
4
+
5
+ require 'madeleine'
6
+
7
+ module Madeleine
8
+ module Clock
9
+
10
+ # Deprecated. Use SnapshotMadeleine instead.
11
+ class ClockedSnapshotMadeleine < ::Madeleine::SnapshotMadeleine # :nodoc:
12
+ end
13
+
14
+ # Let your system extend this module if you need to access the
15
+ # machine time. Used together with a TimeActor that keeps
16
+ # the clock current.
17
+ module ClockedSystem
18
+
19
+ # Returns this system's Clock.
20
+ def clock
21
+ unless defined? @clock
22
+ @clock = Clock.new
23
+ end
24
+ @clock
25
+ end
26
+ end
27
+
28
+ # Sends clock ticks to update a ClockedSystem, so that time can be
29
+ # dealt with in a deterministic way.
30
+ class TimeActor
31
+
32
+ # Create and launch a new TimeActor
33
+ #
34
+ # * <tt>madeleine</tt> - The SnapshotMadeleine instance to work on.
35
+ # * <tt>delay</tt> - Delay between ticks in seconds (Optional).
36
+ def self.launch(madeleine, delay=0.1)
37
+ result = new(madeleine, delay)
38
+ result
39
+ end
40
+
41
+ # Stops the TimeActor.
42
+ def destroy
43
+ @is_destroyed = true
44
+ @thread.wakeup
45
+ @thread.join
46
+ end
47
+
48
+ private_class_method :new
49
+
50
+ private
51
+
52
+ def initialize(madeleine, delay) #:nodoc:
53
+ @madeleine = madeleine
54
+ @is_destroyed = false
55
+ send_tick
56
+ @thread = Thread.new {
57
+ until @is_destroyed
58
+ sleep(delay)
59
+ send_tick
60
+ end
61
+ }
62
+ end
63
+
64
+ def send_tick
65
+ @madeleine.execute_command(Tick.new(Time.now))
66
+ end
67
+ end
68
+
69
+ # Keeps track of time in a ClockedSystem.
70
+ class Clock
71
+ # Returns the system's time as a Ruby <tt>Time</tt>.
72
+ attr_reader :time
73
+
74
+ def initialize
75
+ @time = Time.at(0)
76
+ end
77
+
78
+ def forward_to(newTime)
79
+ @time = newTime
80
+ end
81
+ end
82
+
83
+ #
84
+ # Internal classes below
85
+ #
86
+
87
+ # Deprecated. Merged into default implementation.
88
+ class TimeOptimizingLogger < ::Madeleine::Logger # :nodoc:
89
+ end
90
+
91
+ end
92
+ end
93
+
94
+ ClockedSnapshotMadeleine = Madeleine::Clock::ClockedSnapshotMadeleine