madeleine 0.7.3 → 0.8.0.pre
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/{NEWS → CHANGES.txt} +6 -0
- data/README +5 -28
- data/contrib/batched.rb +3 -2
- data/contrib/benchmark.rb +0 -0
- data/contrib/test_batched.rb +6 -5
- data/contrib/test_scalability.rb +0 -0
- data/contrib/threaded_benchmark.rb +0 -0
- data/lib/madeleine.rb +36 -34
- data/lib/madeleine/clock.rb +4 -19
- data/lib/madeleine/version.rb +3 -0
- data/lib/madeleine/zmarshal.rb +18 -11
- metadata +93 -45
- data/lib/madeleine/automatic.rb +0 -429
data/{NEWS → CHANGES.txt}
RENAMED
@@ -1,3 +1,9 @@
|
|
1
|
+
* Ruby 1.9 compatible (requires at least 1.8.7)
|
2
|
+
* Dropped the 'automatic' feature (Madeleine::Automatic etc)
|
3
|
+
* Dropped support for SOAP marshalling
|
4
|
+
* Removed deprecated classes:
|
5
|
+
- ClockedSnapshotMadeleine
|
6
|
+
- TimeOptimizingLogger
|
1
7
|
|
2
8
|
Madeleine 0.7.3 (June 11, 2006):
|
3
9
|
|
data/README
CHANGED
@@ -3,23 +3,11 @@ Madeleine is a Ruby implementation of Object Prevalence: Transparent
|
|
3
3
|
persistence of business objects using command logging and complete
|
4
4
|
system snapshots.
|
5
5
|
|
6
|
-
|
6
|
+
https://github.com/ghostganz/madeleine
|
7
7
|
|
8
8
|
Madeleine's design is based on Prevayler, the original Java
|
9
9
|
prevalence layer.
|
10
10
|
|
11
|
-
Learn more about object prevalence at <http://www.prevayler.org/>.
|
12
|
-
|
13
|
-
|
14
|
-
Installation:
|
15
|
-
|
16
|
-
Typical installation procedure is:
|
17
|
-
$ ruby install.rb config
|
18
|
-
$ ruby install.rb setup
|
19
|
-
# ruby install.rb install (may require root privilege)
|
20
|
-
Try 'ruby install.rb --help' for detailed usage.
|
21
|
-
|
22
|
-
[From the documentation of Minero Aoki's 'install.rb']
|
23
11
|
|
24
12
|
Usage:
|
25
13
|
|
@@ -40,23 +28,12 @@ Usage:
|
|
40
28
|
|
41
29
|
Requirements:
|
42
30
|
|
43
|
-
* Ruby 1.8.
|
44
|
-
|
45
|
-
Additionaly, some of the sample code also uses ruby/tk.
|
46
|
-
|
47
|
-
|
48
|
-
Known problems:
|
49
|
-
|
50
|
-
* Won't run in some Windows-ports of Ruby due to missing
|
51
|
-
fsync() call.
|
31
|
+
* Ruby 1.8.7 or later
|
52
32
|
|
53
33
|
Contact:
|
54
34
|
|
55
35
|
Homepage:
|
56
|
-
|
57
|
-
|
58
|
-
Questions, bug reports, patches, complaints? Use the mailing list:
|
59
|
-
<http://rubyforge.org/mailman/listinfo/madeleine-devel>
|
36
|
+
https://github.com/ghostganz/madeleine
|
60
37
|
|
61
38
|
License:
|
62
39
|
|
@@ -69,8 +46,8 @@ Credits:
|
|
69
46
|
|
70
47
|
With the help of patches, testing and feedback from:
|
71
48
|
|
72
|
-
Steve Conover, David Heinemeier Hansson, Johan Lind,
|
73
|
-
IIMA Susumu, Martin Tampe and Jon
|
49
|
+
Steve Conover, David Heinemeier Hansson, Johan Lind, Håkan Råberg,
|
50
|
+
IIMA Susumu, Martin Tampe and Jon Tirsén
|
74
51
|
|
75
52
|
Thanks to Klaus Wuestefeld and the Prevayler developers for the
|
76
53
|
model of this software; to Minero Aoki for the installer; to Matz and
|
data/contrib/batched.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
# Batched writes for Madeleine
|
2
3
|
#
|
3
|
-
# Copyright(c)
|
4
|
+
# Copyright(c) Håkan Råberg 2003
|
5
|
+
#
|
4
6
|
#
|
5
|
-
#
|
6
7
|
# This is an experimental implementation of batched log writes to mininize
|
7
8
|
# calls to fsync. It uses a Shared/Exclusive-Lock, implemented in sync.rb,
|
8
9
|
# which is included in Ruby 1.8.
|
data/contrib/benchmark.rb
CHANGED
File without changes
|
data/contrib/test_batched.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/local/bin/ruby -w
|
2
|
+
# -*- coding: utf-8 -*-
|
2
3
|
#
|
3
|
-
# Copyright(c) 2003
|
4
|
+
# Copyright(c) 2003 Håkan Råberg
|
4
5
|
#
|
5
6
|
# Some components taken from test_persistence.rb
|
6
7
|
# Copyright(c) 2003 Anders Bengtsson
|
@@ -14,7 +15,7 @@ require 'madeleine/clock'
|
|
14
15
|
|
15
16
|
|
16
17
|
module Madeleine::Batch
|
17
|
-
class BatchedSnapshotMadeleineTest <
|
18
|
+
class BatchedSnapshotMadeleineTest < MiniTest::Unit::TestCase
|
18
19
|
|
19
20
|
class ArraySystem < Array
|
20
21
|
include Madeleine::Clock::ClockedSystem
|
@@ -121,7 +122,7 @@ module Madeleine::Batch
|
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
124
|
-
class BatchedLogTest <
|
125
|
+
class BatchedLogTest < MiniTest::Unit::TestCase
|
125
126
|
|
126
127
|
class MockMadeleine
|
127
128
|
def initialize(logger)
|
@@ -236,10 +237,10 @@ def add_batched_tests(suite)
|
|
236
237
|
end
|
237
238
|
|
238
239
|
if __FILE__ == $0
|
239
|
-
suite =
|
240
|
+
suite = MiniTest::Unit::TestSuite.new("BatchedLogTest")
|
240
241
|
add_batched_tests(suite)
|
241
242
|
|
242
243
|
require 'test/unit/ui/console/testrunner'
|
243
244
|
Thread.abort_on_exception = true
|
244
|
-
|
245
|
+
MiniTest::Unit::UI::Console::TestRunner.run(suite)
|
245
246
|
end
|
data/contrib/test_scalability.rb
CHANGED
File without changes
|
File without changes
|
data/lib/madeleine.rb
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
# require 'madeleine'
|
10
10
|
#
|
11
11
|
# madeleine = SnapshotMadeleine.new("my_example_storage") {
|
12
|
-
# SomeExampleApplication.new
|
12
|
+
# SomeExampleApplication.new
|
13
13
|
# }
|
14
14
|
#
|
15
15
|
# madeleine.execute_command(command)
|
@@ -22,8 +22,9 @@ module Madeleine
|
|
22
22
|
require 'fileutils'
|
23
23
|
require 'madeleine/files'
|
24
24
|
require 'madeleine/sanity'
|
25
|
+
require 'madeleine/version'
|
25
26
|
|
26
|
-
MADELEINE_VERSION =
|
27
|
+
MADELEINE_VERSION = Madeleine::VERSION
|
27
28
|
|
28
29
|
class SnapshotMadeleine
|
29
30
|
|
@@ -33,7 +34,7 @@ module Madeleine
|
|
33
34
|
# then be restored from the command logs.
|
34
35
|
#
|
35
36
|
# You can provide your own snapshot marshaller, for instance using
|
36
|
-
# YAML
|
37
|
+
# YAML, instead of Ruby's built-in marshaller. The
|
37
38
|
# <tt>snapshot_marshaller</tt> must respond to
|
38
39
|
# <tt>load(stream)</tt> and <tt>dump(object, stream)</tt>. You
|
39
40
|
# must use the same marshaller every time for a system.
|
@@ -87,11 +88,11 @@ module Madeleine
|
|
87
88
|
# * <tt>command</tt> - The command to execute on the system.
|
88
89
|
def execute_command(command)
|
89
90
|
verify_command_sane(command)
|
90
|
-
@lock.synchronize
|
91
|
+
@lock.synchronize do
|
91
92
|
raise MadeleineClosedException if @closed
|
92
93
|
@logger.store(command)
|
93
94
|
@executer.execute(command)
|
94
|
-
|
95
|
+
end
|
95
96
|
end
|
96
97
|
|
97
98
|
# Execute a query on the prevalent system.
|
@@ -102,9 +103,9 @@ module Madeleine
|
|
102
103
|
#
|
103
104
|
# * <tt>query</tt> - The query command to execute
|
104
105
|
def execute_query(query)
|
105
|
-
@lock.synchronize_shared
|
106
|
+
@lock.synchronize_shared do
|
106
107
|
@executer.execute(query)
|
107
|
-
|
108
|
+
end
|
108
109
|
end
|
109
110
|
|
110
111
|
# Take a snapshot of the current system.
|
@@ -123,11 +124,11 @@ module Madeleine
|
|
123
124
|
# end
|
124
125
|
# }
|
125
126
|
def take_snapshot
|
126
|
-
@lock.synchronize
|
127
|
+
@lock.synchronize do
|
127
128
|
@logger.close
|
128
129
|
@snapshotter.take(@system)
|
129
130
|
@logger.reset
|
130
|
-
|
131
|
+
end
|
131
132
|
end
|
132
133
|
|
133
134
|
# Close the system.
|
@@ -135,10 +136,10 @@ module Madeleine
|
|
135
136
|
# The log file is closed and no new commands can be received
|
136
137
|
# by this Madeleine.
|
137
138
|
def close
|
138
|
-
@lock.synchronize
|
139
|
+
@lock.synchronize do
|
139
140
|
@logger.close
|
140
141
|
@closed = true
|
141
|
-
|
142
|
+
end
|
142
143
|
end
|
143
144
|
|
144
145
|
private
|
@@ -213,9 +214,9 @@ module Madeleine
|
|
213
214
|
id = SnapshotFile.highest_id(@directory_name)
|
214
215
|
if id > 0
|
215
216
|
snapshot_file = SnapshotFile.new(@directory_name, id).name
|
216
|
-
open(snapshot_file, "rb")
|
217
|
+
open(snapshot_file, "rb") do |snapshot|
|
217
218
|
system = @marshaller.load(snapshot)
|
218
|
-
|
219
|
+
end
|
219
220
|
else
|
220
221
|
system = new_system_block.call
|
221
222
|
end
|
@@ -223,19 +224,19 @@ module Madeleine
|
|
223
224
|
end
|
224
225
|
|
225
226
|
def recover_logs(executer)
|
226
|
-
executer.recovery
|
227
|
-
CommandLog.log_file_names(@directory_name, FileService.new).each
|
228
|
-
open(@directory_name + File::SEPARATOR + file_name, "rb")
|
227
|
+
executer.recovery do
|
228
|
+
CommandLog.log_file_names(@directory_name, FileService.new).each do |file_name|
|
229
|
+
open(@directory_name + File::SEPARATOR + file_name, "rb") do |log|
|
229
230
|
recover_log(executer, log)
|
230
|
-
|
231
|
-
|
232
|
-
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
233
234
|
end
|
234
235
|
|
235
236
|
private
|
236
237
|
|
237
238
|
def recover_log(executer, log)
|
238
|
-
|
239
|
+
until log.eof?
|
239
240
|
command = Marshal.load(log)
|
240
241
|
executer.execute(command)
|
241
242
|
end
|
@@ -264,9 +265,10 @@ module Madeleine
|
|
264
265
|
result = file_service.dir_entries(directory_name).select {|name|
|
265
266
|
name =~ /^\d{#{FILE_COUNTER_SIZE}}\.command_log$/
|
266
267
|
}
|
267
|
-
result.each
|
268
|
-
|
269
|
-
|
268
|
+
result.each do |name|
|
269
|
+
name.untaint
|
270
|
+
end
|
271
|
+
result.sort
|
270
272
|
end
|
271
273
|
|
272
274
|
def initialize(path, file_service)
|
@@ -287,13 +289,13 @@ module Madeleine
|
|
287
289
|
|
288
290
|
def self.highest_log(directory_name, file_service)
|
289
291
|
highest = 0
|
290
|
-
log_file_names(directory_name, file_service).each
|
292
|
+
log_file_names(directory_name, file_service).each do |file_name|
|
291
293
|
match = /^(\d{#{FILE_COUNTER_SIZE}})/.match(file_name)
|
292
294
|
n = match[1].to_i
|
293
295
|
if n > highest
|
294
296
|
highest = n
|
295
297
|
end
|
296
|
-
|
298
|
+
end
|
297
299
|
highest
|
298
300
|
end
|
299
301
|
end
|
@@ -315,7 +317,7 @@ module Madeleine
|
|
315
317
|
end
|
316
318
|
|
317
319
|
def ensure_directory_exists
|
318
|
-
|
320
|
+
unless File.exist?(@directory_name)
|
319
321
|
FileUtils.mkpath(@directory_name)
|
320
322
|
end
|
321
323
|
end
|
@@ -353,10 +355,11 @@ module Madeleine
|
|
353
355
|
private
|
354
356
|
|
355
357
|
def delete_log_files
|
356
|
-
Dir.glob(@directory_name + File::SEPARATOR + "*.command_log")
|
358
|
+
names = Dir.glob(@directory_name + File::SEPARATOR + "*.command_log")
|
359
|
+
names.each do |name|
|
357
360
|
name.untaint
|
358
361
|
File.delete(name)
|
359
|
-
|
362
|
+
end
|
360
363
|
end
|
361
364
|
|
362
365
|
def open_new_log
|
@@ -370,14 +373,14 @@ module Madeleine
|
|
370
373
|
return 0 unless File.exist?(directory_name)
|
371
374
|
suffix = "snapshot"
|
372
375
|
highest = 0
|
373
|
-
Dir.foreach(directory_name)
|
376
|
+
Dir.foreach(directory_name) do |file_name|
|
374
377
|
match = /^(\d{#{FILE_COUNTER_SIZE}}\.#{suffix}$)/.match(file_name)
|
375
378
|
next unless match
|
376
379
|
n = match[1].to_i
|
377
380
|
if n > highest
|
378
381
|
highest = n
|
379
382
|
end
|
380
|
-
|
383
|
+
end
|
381
384
|
highest
|
382
385
|
end
|
383
386
|
|
@@ -399,12 +402,12 @@ module Madeleine
|
|
399
402
|
def take(system)
|
400
403
|
numbered_file = SnapshotFile.next(@directory_name)
|
401
404
|
name = numbered_file.name
|
402
|
-
open(name
|
405
|
+
open("#{name}.tmp", 'wb') do |snapshot|
|
403
406
|
@marshaller.dump(system, snapshot)
|
404
407
|
snapshot.flush
|
405
408
|
snapshot.fsync
|
406
|
-
|
407
|
-
File.rename(name
|
409
|
+
end
|
410
|
+
File.rename("#{name}.tmp", name)
|
408
411
|
end
|
409
412
|
end
|
410
413
|
|
@@ -423,4 +426,3 @@ module Madeleine
|
|
423
426
|
end
|
424
427
|
|
425
428
|
SnapshotMadeleine = Madeleine::SnapshotMadeleine
|
426
|
-
|
data/lib/madeleine/clock.rb
CHANGED
@@ -7,10 +7,6 @@ require 'madeleine'
|
|
7
7
|
module Madeleine
|
8
8
|
module Clock
|
9
9
|
|
10
|
-
# Deprecated. Use SnapshotMadeleine instead.
|
11
|
-
class ClockedSnapshotMadeleine < ::Madeleine::SnapshotMadeleine # :nodoc:
|
12
|
-
end
|
13
|
-
|
14
10
|
# Let your system extend this module if you need to access the
|
15
11
|
# machine time. Used together with a TimeActor that keeps
|
16
12
|
# the clock current.
|
@@ -53,12 +49,12 @@ module Madeleine
|
|
53
49
|
@madeleine = madeleine
|
54
50
|
@is_destroyed = false
|
55
51
|
send_tick
|
56
|
-
@thread = Thread.new
|
52
|
+
@thread = Thread.new do
|
57
53
|
until @is_destroyed
|
58
54
|
sleep(delay)
|
59
55
|
send_tick
|
60
56
|
end
|
61
|
-
|
57
|
+
end
|
62
58
|
end
|
63
59
|
|
64
60
|
def send_tick
|
@@ -75,20 +71,9 @@ module Madeleine
|
|
75
71
|
@time = Time.at(0)
|
76
72
|
end
|
77
73
|
|
78
|
-
def forward_to(
|
79
|
-
@time =
|
74
|
+
def forward_to(new_time)
|
75
|
+
@time = new_time
|
80
76
|
end
|
81
77
|
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
78
|
end
|
92
79
|
end
|
93
|
-
|
94
|
-
ClockedSnapshotMadeleine = Madeleine::Clock::ClockedSnapshotMadeleine
|
data/lib/madeleine/zmarshal.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Anders Bengtsson <ndrsbngtssn@yahoo.se>
|
3
|
-
# Copyright:: Copyright (c) 2004
|
3
|
+
# Copyright:: Copyright (c) 2004-2012
|
4
4
|
#
|
5
5
|
|
6
6
|
require 'zlib'
|
@@ -14,8 +14,7 @@ module Madeleine
|
|
14
14
|
#
|
15
15
|
# Uses <tt>zlib</tt> to do on-the-fly compression/decompression.
|
16
16
|
#
|
17
|
-
# ZMarshal works with Ruby's own Marshal and YAML
|
18
|
-
# marshalling.
|
17
|
+
# ZMarshal works with Ruby's own Marshal and YAML
|
19
18
|
#
|
20
19
|
# Usage:
|
21
20
|
#
|
@@ -24,7 +23,7 @@ module Madeleine
|
|
24
23
|
#
|
25
24
|
# marshaller = Madeleine::ZMarshal.new(YAML)
|
26
25
|
# madeleine = SnapshotMadeleine.new("my_example_storage", marshaller) {
|
27
|
-
# SomeExampleApplication.new
|
26
|
+
# SomeExampleApplication.new
|
28
27
|
# }
|
29
28
|
#
|
30
29
|
class ZMarshal
|
@@ -34,14 +33,9 @@ module Madeleine
|
|
34
33
|
end
|
35
34
|
|
36
35
|
def load(stream)
|
37
|
-
zstream =
|
36
|
+
zstream = WorkaroundGzipReader.new(stream)
|
38
37
|
begin
|
39
|
-
|
40
|
-
# Marshal's 0-sized reads and SOAP can't handle streams at all.
|
41
|
-
# In a bright future we can revert to reading directly from the
|
42
|
-
# stream again.
|
43
|
-
buffer = zstream.read
|
44
|
-
return @marshaller.load(buffer)
|
38
|
+
return @marshaller.load(zstream)
|
45
39
|
ensure
|
46
40
|
zstream.finish
|
47
41
|
end
|
@@ -56,5 +50,18 @@ module Madeleine
|
|
56
50
|
end
|
57
51
|
nil
|
58
52
|
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
class WorkaroundGzipReader < Zlib::GzipReader
|
57
|
+
# The 'psych' YAML parser, default since Ruby 1.9.3,
|
58
|
+
# assumes that its input IO has an external_encoding()
|
59
|
+
# method.
|
60
|
+
unless defined? external_encoding
|
61
|
+
def external_encoding
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
59
66
|
end
|
60
67
|
end
|
metadata
CHANGED
@@ -1,63 +1,111 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.8.11
|
3
|
-
specification_version: 1
|
1
|
+
--- !ruby/object:Gem::Specification
|
4
2
|
name: madeleine
|
5
|
-
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
|
8
|
-
summary: Madeleine is a Ruby implementation of Object Prevalence
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: ndrsbngtssn@yahoo.se
|
12
|
-
homepage: http://madeleine.rubyforge.org
|
13
|
-
rubyforge_project:
|
14
|
-
description:
|
15
|
-
autorequire: madeleine
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: false
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 1.8.1
|
24
|
-
version:
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0.pre
|
5
|
+
prerelease: 6
|
25
6
|
platform: ruby
|
26
|
-
|
27
|
-
cert_chain:
|
28
|
-
authors:
|
7
|
+
authors:
|
29
8
|
- Anders Bengtsson
|
30
|
-
|
31
|
-
|
32
|
-
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 4.3.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.3.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rdoc
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Transparent persistence of system state using logging and snapshots
|
63
|
+
email: ndrsbngtssn@yahoo.se
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
33
68
|
- lib/madeleine/clock.rb
|
34
69
|
- lib/madeleine/files.rb
|
35
70
|
- lib/madeleine/sanity.rb
|
71
|
+
- lib/madeleine/version.rb
|
36
72
|
- lib/madeleine/zmarshal.rb
|
37
|
-
-
|
73
|
+
- lib/madeleine.rb
|
38
74
|
- samples/clock_click.rb
|
75
|
+
- samples/dictionary_client.rb
|
39
76
|
- samples/dictionary_server.rb
|
40
77
|
- samples/painter.rb
|
41
|
-
- contrib/test_batched.rb
|
42
78
|
- contrib/batched.rb
|
43
79
|
- contrib/benchmark.rb
|
44
|
-
- contrib/threaded_benchmark.rb
|
45
80
|
- contrib/create_command.rb
|
81
|
+
- contrib/test_batched.rb
|
46
82
|
- contrib/test_scalability.rb
|
83
|
+
- contrib/threaded_benchmark.rb
|
47
84
|
- README
|
48
|
-
-
|
85
|
+
- CHANGES.txt
|
49
86
|
- COPYING
|
50
|
-
|
51
|
-
|
87
|
+
homepage: http://github.com/ghostganz/madeleine
|
88
|
+
licenses: []
|
89
|
+
post_install_message:
|
52
90
|
rdoc_options: []
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.8.7
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>'
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.3.1
|
60
105
|
requirements: []
|
61
|
-
|
62
|
-
|
63
|
-
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.8.24
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Madeleine is a Ruby implementation of Object Prevalence
|
111
|
+
test_files: []
|
data/lib/madeleine/automatic.rb
DELETED
@@ -1,429 +0,0 @@
|
|
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-2006
|
11
|
-
# Version:: 0.42
|
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.system.foo # not logged
|
56
|
-
# print mad.system.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
|
-
@read_only_methods ||= []
|
80
|
-
Prox.new(_old_new(*args, &block))
|
81
|
-
end
|
82
|
-
#
|
83
|
-
# Called when a method added - remember symbol if read only
|
84
|
-
# This is a good place to add in any superclass's read only methods also
|
85
|
-
#
|
86
|
-
def method_added(symbol)
|
87
|
-
self.instance_eval {
|
88
|
-
@read_only_methods ||= []
|
89
|
-
@auto_read_only_flag ||= false
|
90
|
-
@read_only_methods << symbol if @auto_read_only_flag
|
91
|
-
c = self
|
92
|
-
while (c = c.superclass)
|
93
|
-
if (c.instance_eval {instance_variables.include? "@read_only_methods"})
|
94
|
-
@read_only_methods |= c.instance_eval {@read_only_methods}
|
95
|
-
end
|
96
|
-
end
|
97
|
-
}
|
98
|
-
end
|
99
|
-
#
|
100
|
-
# Set the read only flag, or add read only methods
|
101
|
-
#
|
102
|
-
def automatic_read_only(*list)
|
103
|
-
if (list == [])
|
104
|
-
self.instance_eval {@auto_read_only_flag = true}
|
105
|
-
else
|
106
|
-
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods << s}}
|
107
|
-
end
|
108
|
-
end
|
109
|
-
#
|
110
|
-
# Clear the read only flag, or remove read only methods
|
111
|
-
#
|
112
|
-
def automatic_read_write(*list)
|
113
|
-
if (list == [])
|
114
|
-
self.instance_eval {@auto_read_only_flag = false}
|
115
|
-
else
|
116
|
-
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods.delete(s)}}
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
end
|
121
|
-
end
|
122
|
-
#
|
123
|
-
# Return the list of read only methods so Automatic_proxy#method_missing can find what to and what not to make into a command
|
124
|
-
#
|
125
|
-
def read_only_methods
|
126
|
-
self.class.instance_eval {@read_only_methods}
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
#
|
131
|
-
# A Command object is automatically created for each method call to an object within the system that comes from without.
|
132
|
-
# These objects are recorded in the log by Madeleine.
|
133
|
-
#
|
134
|
-
class Command
|
135
|
-
def initialize(symbol, myid, *args)
|
136
|
-
@symbol = symbol
|
137
|
-
@myid = myid
|
138
|
-
@args = args
|
139
|
-
end
|
140
|
-
#
|
141
|
-
# Called by madeleine when the command is done either first time, or when restoring the log
|
142
|
-
#
|
143
|
-
def execute(system)
|
144
|
-
Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
#
|
148
|
-
# This is a little class to pass to SnapshotMadeleine. This is used for snapshots only.
|
149
|
-
# It acts as the marshaller, and just passes marshalling requests on to the user specified
|
150
|
-
# marshaller. This defaults to Marshal, but could be YAML or another.
|
151
|
-
# After we have done a restore, the ObjectSpace is searched for instances of Prox to
|
152
|
-
# add new objects to the list in AutomaticSnapshotMadeleine
|
153
|
-
#
|
154
|
-
class Automatic_marshaller #:nodoc:
|
155
|
-
def Automatic_marshaller.load(io)
|
156
|
-
restored_obj = Deserialize.load(io, Thread.current[:system].marshaller)
|
157
|
-
p restored_obj if restored_obj.class != Prox
|
158
|
-
ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)}
|
159
|
-
restored_obj
|
160
|
-
end
|
161
|
-
def Automatic_marshaller.dump(obj, io = nil)
|
162
|
-
Thread.current[:system].marshaller.dump(obj, io)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
#
|
166
|
-
# A Prox object is generated and returned by Interceptor each time a system object is created.
|
167
|
-
#
|
168
|
-
class Prox #:nodoc:
|
169
|
-
attr_accessor :thing, :myid, :sysid
|
170
|
-
|
171
|
-
def initialize(thing)
|
172
|
-
if (thing)
|
173
|
-
raise "App object created outside of app" unless Thread.current[:system]
|
174
|
-
@sysid = Thread.current[:system].sysid
|
175
|
-
@myid = Thread.current[:system].add(self)
|
176
|
-
@thing = thing
|
177
|
-
end
|
178
|
-
end
|
179
|
-
#
|
180
|
-
# This automatically makes and executes a new Command if a method is called from
|
181
|
-
# outside the system.
|
182
|
-
#
|
183
|
-
def method_missing(symbol, *args, &block)
|
184
|
-
# print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n"
|
185
|
-
raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol)
|
186
|
-
if (Thread.current[:system])
|
187
|
-
@thing.send(symbol, *args, &block)
|
188
|
-
else
|
189
|
-
raise "Cannot make command with block" if block_given?
|
190
|
-
Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid]
|
191
|
-
begin
|
192
|
-
if (@thing.read_only_methods.include?(symbol))
|
193
|
-
result = Thread.current[:system].execute_query(Command.new(symbol, @myid, *args))
|
194
|
-
else
|
195
|
-
result = Thread.current[:system].execute_command(Command.new(symbol, @myid, *args))
|
196
|
-
end
|
197
|
-
ensure
|
198
|
-
Thread.current[:system] = false
|
199
|
-
end
|
200
|
-
result
|
201
|
-
end
|
202
|
-
end
|
203
|
-
#
|
204
|
-
# Custom marshalling - this adds the internal id (myid) and the system id to a marshal
|
205
|
-
# of the object we are the proxy for.
|
206
|
-
# We take care to not marshal the same object twice, so circular references will work.
|
207
|
-
# We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and
|
208
|
-
# marshal is always used for Command objects
|
209
|
-
#
|
210
|
-
def _dump(depth)
|
211
|
-
if (Thread.current[:snapshot_memory])
|
212
|
-
if (Thread.current[:snapshot_memory][self])
|
213
|
-
[@myid.to_s, @sysid].pack("A8A30")
|
214
|
-
else
|
215
|
-
Thread.current[:snapshot_memory][self] = true
|
216
|
-
[@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth)
|
217
|
-
end
|
218
|
-
else
|
219
|
-
[@myid.to_s, @sysid].pack("A8A30") # never marshal a prox object in a command, just ref
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
#
|
224
|
-
# Custom marshalling for Marshal - restore a Prox object.
|
225
|
-
#
|
226
|
-
def Prox._load(str)
|
227
|
-
x = Prox.new(nil)
|
228
|
-
a = str.unpack("A8A30a*")
|
229
|
-
x.myid = a[0].to_i
|
230
|
-
x.sysid = a[1]
|
231
|
-
x = Thread.current[:system].restore(x)
|
232
|
-
x.thing = Marshal.load(a[2]) if (a[2] > "")
|
233
|
-
x
|
234
|
-
end
|
235
|
-
|
236
|
-
end
|
237
|
-
|
238
|
-
#
|
239
|
-
# The AutomaticSnapshotMadeleine class contains an instance of the persister
|
240
|
-
# (default is SnapshotMadeleine) and provides additional automatic functionality.
|
241
|
-
#
|
242
|
-
# The class is instantiated the same way as SnapshotMadeleine:
|
243
|
-
# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
|
244
|
-
# The second initialisation parameter is the persister. Supported persisters are:
|
245
|
-
#
|
246
|
-
# * Marshal (default)
|
247
|
-
# * YAML
|
248
|
-
# * SOAP::Marshal
|
249
|
-
# * Madeleine::ZMarshal.new(Marshal)
|
250
|
-
# * Madeleine::ZMarshal.new(YAML)
|
251
|
-
# * Madeleine::ZMarshal.new(SOAP::Marshal)
|
252
|
-
#
|
253
|
-
# The class keeps a record of all the systems that currently exist.
|
254
|
-
# Each instance of the class keeps a record of Prox objects in that system by internal id (myid).
|
255
|
-
#
|
256
|
-
# We also add functionality to take_snapshot in order to set things up so that the custom Prox object
|
257
|
-
# marshalling will work correctly.
|
258
|
-
#
|
259
|
-
class AutomaticSnapshotMadeleine
|
260
|
-
attr_accessor :marshaller
|
261
|
-
attr_reader :list, :sysid
|
262
|
-
|
263
|
-
def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block)
|
264
|
-
@sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid
|
265
|
-
@myid_count = 0
|
266
|
-
@list = {}
|
267
|
-
Thread.current[:system] = self # during system startup system should not create commands
|
268
|
-
Thread.critical = true
|
269
|
-
@@systems ||= {} # holds systems by sysid
|
270
|
-
@@systems[@sysid] = self
|
271
|
-
Thread.critical = false
|
272
|
-
@marshaller = marshaller # until attrb
|
273
|
-
|
274
|
-
begin
|
275
|
-
@persister = persister.new(directory_name, Automatic_marshaller, &new_system_block)
|
276
|
-
@list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid
|
277
|
-
begin
|
278
|
-
obj = ObjectSpace._id2ref(v)
|
279
|
-
raise unless obj.respond_to?(:sysid=)
|
280
|
-
obj.sysid = @sysid
|
281
|
-
false
|
282
|
-
rescue RangeError
|
283
|
-
true # Id was to a GC'd object, delete it
|
284
|
-
rescue RuntimeError
|
285
|
-
true # GC'd object, and id was reused for something else
|
286
|
-
end
|
287
|
-
}
|
288
|
-
ensure
|
289
|
-
Thread.current[:system] = false
|
290
|
-
end
|
291
|
-
end
|
292
|
-
#
|
293
|
-
# Add a proxy object to the list, return the myid for that object
|
294
|
-
#
|
295
|
-
def add(proxo)
|
296
|
-
@list[@myid_count += 1] = proxo.object_id
|
297
|
-
@myid_count
|
298
|
-
end
|
299
|
-
#
|
300
|
-
# Restore a marshalled proxy object to list - myid_count is increased as required.
|
301
|
-
# If the object already exists in the system then the existing object must be used.
|
302
|
-
#
|
303
|
-
def restore(proxo)
|
304
|
-
if (@list[proxo.myid])
|
305
|
-
proxo = myid2ref(proxo.myid)
|
306
|
-
else
|
307
|
-
@list[proxo.myid] = proxo.object_id
|
308
|
-
@myid_count = proxo.myid if (@myid_count < proxo.myid)
|
309
|
-
end
|
310
|
-
proxo
|
311
|
-
end
|
312
|
-
#
|
313
|
-
# Returns a reference to the object indicated by the internal id supplied.
|
314
|
-
#
|
315
|
-
def myid2ref(myid)
|
316
|
-
raise "Internal id #{myid} not found" unless objid = @list[myid]
|
317
|
-
ObjectSpace._id2ref(objid)
|
318
|
-
end
|
319
|
-
#
|
320
|
-
# Take a snapshot of the system.
|
321
|
-
#
|
322
|
-
def take_snapshot
|
323
|
-
begin
|
324
|
-
Thread.current[:system] = self
|
325
|
-
Thread.current[:snapshot_memory] = {}
|
326
|
-
@persister.take_snapshot
|
327
|
-
ensure
|
328
|
-
Thread.current[:snapshot_memory] = nil
|
329
|
-
Thread.current[:system] = false
|
330
|
-
end
|
331
|
-
end
|
332
|
-
#
|
333
|
-
# Returns the hash containing the systems.
|
334
|
-
#
|
335
|
-
def AutomaticSnapshotMadeleine.systems
|
336
|
-
@@systems
|
337
|
-
end
|
338
|
-
#
|
339
|
-
# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new
|
340
|
-
# system before GC gets them
|
341
|
-
#
|
342
|
-
def close
|
343
|
-
begin
|
344
|
-
@list.each_key {|k|
|
345
|
-
ref = myid2ref(k)
|
346
|
-
ref.sysid = nil if ref.class == Prox
|
347
|
-
}
|
348
|
-
rescue RangeError
|
349
|
-
# do nothing
|
350
|
-
end
|
351
|
-
@persister.close
|
352
|
-
end
|
353
|
-
|
354
|
-
#
|
355
|
-
# Pass on any other calls to the persister
|
356
|
-
#
|
357
|
-
def method_missing(symbol, *args, &block)
|
358
|
-
@persister.send(symbol, *args, &block)
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
|
363
|
-
module Deserialize #:nodoc:
|
364
|
-
#
|
365
|
-
# Detect format of an io stream. Leave it rewound.
|
366
|
-
#
|
367
|
-
def Deserialize.detect(io)
|
368
|
-
c = io.getc
|
369
|
-
c1 = io.getc
|
370
|
-
io.rewind
|
371
|
-
if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION)
|
372
|
-
Marshal
|
373
|
-
elsif (c == 31 && c1 == 139) # gzip magic numbers
|
374
|
-
ZMarshal
|
375
|
-
else
|
376
|
-
while (s = io.gets)
|
377
|
-
break if (s !~ /^\s*$/) # ignore blank lines
|
378
|
-
end
|
379
|
-
io.rewind
|
380
|
-
if (s && s =~ /^\s*<\?[xX][mM][lL]/) # "<?xml" begins an xml serialization
|
381
|
-
SOAP::Marshal
|
382
|
-
else
|
383
|
-
while (s = io.gets)
|
384
|
-
break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines
|
385
|
-
end
|
386
|
-
io.rewind
|
387
|
-
if (s && s =~ /^\s*---/) # "---" is the yaml header
|
388
|
-
YAML
|
389
|
-
else
|
390
|
-
nil # failed to detect
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
395
|
-
#
|
396
|
-
# Try to deserialize object. If there was an error, try to detect marshal format,
|
397
|
-
# and return deserialized object using the right marshaller
|
398
|
-
# If detection didn't work, raise up the exception
|
399
|
-
#
|
400
|
-
def Deserialize.load(io, marshaller=Marshal)
|
401
|
-
begin
|
402
|
-
raise "Must detect with YAML" if marshaller == YAML
|
403
|
-
marshaller.load(io)
|
404
|
-
rescue Exception => e
|
405
|
-
io.rewind
|
406
|
-
detected_marshaller = detect(io)
|
407
|
-
if (detected_marshaller == ZMarshal)
|
408
|
-
zio = Zlib::GzipReader.new(io)
|
409
|
-
detected_zmarshaller = detect(zio)
|
410
|
-
zio.finish
|
411
|
-
io.rewind
|
412
|
-
if (detected_zmarshaller)
|
413
|
-
ZMarshal.new(detected_zmarshaller).load(io)
|
414
|
-
else
|
415
|
-
raise e
|
416
|
-
end
|
417
|
-
elsif (detected_marshaller)
|
418
|
-
detected_marshaller.load(io)
|
419
|
-
else
|
420
|
-
raise e
|
421
|
-
end
|
422
|
-
end
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine
|