madeleine 0.7.1

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.
@@ -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