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.
- data/COPYING +31 -0
- data/NEWS +55 -0
- data/README +78 -0
- data/contrib/batched.rb +298 -0
- data/contrib/benchmark.rb +35 -0
- data/contrib/test_batched.rb +245 -0
- data/contrib/test_scalability.rb +248 -0
- data/contrib/threaded_benchmark.rb +44 -0
- data/lib/madeleine.rb +419 -0
- data/lib/madeleine/automatic.rb +418 -0
- data/lib/madeleine/clock.rb +94 -0
- data/lib/madeleine/files.rb +19 -0
- data/lib/madeleine/zmarshal.rb +60 -0
- data/samples/clock_click.rb +73 -0
- data/samples/dictionary_client.rb +23 -0
- data/samples/dictionary_server.rb +94 -0
- data/samples/painter.rb +60 -0
- metadata +53 -0
@@ -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
|