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
data/COPYING
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2003-2004, Anders Bengtsson
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions
|
6
|
+
are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright
|
12
|
+
notice, this list of conditions and the following disclaimer in the
|
13
|
+
documentation and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. The names of its contributors may not be used to endorse or promote
|
16
|
+
products derived from this software without specific prior written
|
17
|
+
permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
20
|
+
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
21
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
22
|
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
24
|
+
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
25
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
26
|
+
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
27
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
28
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
29
|
+
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
30
|
+
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
31
|
+
SUCH DAMAGE.
|
data/NEWS
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
Madeleine 0.7.1 (August 22, 2004):
|
3
|
+
|
4
|
+
* ZMarshal changed to work around Zlib bug.
|
5
|
+
* automatic_read_only fixed when intercepted class is inherited from
|
6
|
+
|
7
|
+
Madeleine 0.7 (July 23, 2004):
|
8
|
+
|
9
|
+
* Broken clock unit test on win32 fixed.
|
10
|
+
* AutomaticSnapshotMadeleine detects snapshot format on recovery
|
11
|
+
* Snapshot compression with Madeleine::ZMarshal
|
12
|
+
* YAML snapshots supported for automatic commands
|
13
|
+
* SOAP snapshots supported for automatic commands
|
14
|
+
* Read-only methods for automatic commands
|
15
|
+
|
16
|
+
Madeleine 0.6.1 (March 30, 2004):
|
17
|
+
|
18
|
+
* Bug fix: Use binary mode for I/O, fixes log replay
|
19
|
+
on mswin32 port of Ruby (Patch from Martin Tampe)
|
20
|
+
|
21
|
+
Madeleine 0.6 (March 28, 2004):
|
22
|
+
|
23
|
+
* Changed license to BSD
|
24
|
+
* Added a RubyGem specification
|
25
|
+
* Re-designed initialization (but still backward-compatible)
|
26
|
+
* Bug fix: Fixed use of finalized object's id in AutomaticSnapshotMadeleine
|
27
|
+
|
28
|
+
Madeleine 0.5 (August 31, 2003):
|
29
|
+
|
30
|
+
* Bug fix: Log order on recovery was wrong on some platforms
|
31
|
+
(Reported by IIMA Susumu)
|
32
|
+
* No longer requires the system clock to always increase
|
33
|
+
* Shared locks for queries
|
34
|
+
|
35
|
+
Madeleine 0.4 (July 4, 2003):
|
36
|
+
|
37
|
+
* Deprecated ClockedSnapshotMadeleine
|
38
|
+
* Added execute_query()
|
39
|
+
* API documentation in RDoc format
|
40
|
+
|
41
|
+
Madeleine 0.3 (May 15, 2003):
|
42
|
+
|
43
|
+
* Automatic commands
|
44
|
+
* Some classes exported to the default module
|
45
|
+
* Clock support not loaded by default (require 'madeleine/clock')
|
46
|
+
* Bug fix: Error handling when replaying logged commands.
|
47
|
+
* New system through block instead of argument (API change)
|
48
|
+
* Works in $SAFE = 1
|
49
|
+
|
50
|
+
Madeleine 0.2:
|
51
|
+
|
52
|
+
* Supports custom marshalling implementations.
|
53
|
+
* Changed interface for ClockedSystem and Clock.
|
54
|
+
* Some documentation added, including API docs.
|
55
|
+
|
data/README
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
Madeleine is a Ruby implementation of Object Prevalence: Transparent
|
3
|
+
persistence of business objects using command logging and complete
|
4
|
+
system snapshots.
|
5
|
+
|
6
|
+
<http://madeleine.sourceforge.net/>
|
7
|
+
|
8
|
+
Madeleine's design is based on Prevayler, the original Java
|
9
|
+
prevalence layer.
|
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
|
+
|
24
|
+
Usage:
|
25
|
+
|
26
|
+
require 'madeleine'
|
27
|
+
|
28
|
+
# Create an application as a prevalent system
|
29
|
+
|
30
|
+
madeleine = SnapshotMadeleine.new("my_example_storage") {
|
31
|
+
SomeExampleApplication.new()
|
32
|
+
}
|
33
|
+
|
34
|
+
# Do modifications of the system by sending commands through
|
35
|
+
# the Madeleine instance. A command is an object with a suitable
|
36
|
+
# "execute(system)" method.
|
37
|
+
|
38
|
+
madeleine.execute_command(command)
|
39
|
+
|
40
|
+
|
41
|
+
Requirements:
|
42
|
+
|
43
|
+
* Ruby 1.8.1 or later
|
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.
|
52
|
+
|
53
|
+
Contact:
|
54
|
+
|
55
|
+
Homepage:
|
56
|
+
<http://madeleine.sourceforge.net/>
|
57
|
+
|
58
|
+
Questions, bug reports, patches, complaints? Use the mailing list:
|
59
|
+
<http://lists.sourceforge.net/lists/listinfo/madeleine-devel>
|
60
|
+
|
61
|
+
License:
|
62
|
+
|
63
|
+
BSD (see the file COPYING)
|
64
|
+
|
65
|
+
Credits:
|
66
|
+
|
67
|
+
Anders Bengtsson - Prevalence core impl.
|
68
|
+
Stephen Sykes - Automatic commands impl.
|
69
|
+
|
70
|
+
With the help of patches, testing and feedback from:
|
71
|
+
|
72
|
+
Steve Conover, David Heinemeier Hansson, Johan Lind, H�kan R�berg,
|
73
|
+
IIMA Susumu, Martin Tampe and Jon Tirs�n
|
74
|
+
|
75
|
+
Thanks to Klaus Wuestefeld and the Prevayler developers for the
|
76
|
+
model of this software; to Minero Aoki for the installer; to Matz and
|
77
|
+
the core developers for the Ruby language!
|
78
|
+
|
data/contrib/batched.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# Batched writes for Madeleine
|
2
|
+
#
|
3
|
+
# Copyright(c) H�kan R�berg 2003
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# This is an experimental implementation of batched log writes to mininize
|
7
|
+
# calls to fsync. It uses a Shared/Exclusive-Lock, implemented in sync.rb,
|
8
|
+
# which is included in Ruby 1.8.
|
9
|
+
#
|
10
|
+
# Writes are batched for a specified amount of time, before written to disk and
|
11
|
+
# then executed.
|
12
|
+
#
|
13
|
+
# For a detailed discussion about the problem, see
|
14
|
+
# http://www.prevayler.org/wiki.jsp?topic=OvercomingTheWriteBottleneck
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# Usage is identical to normal SnapshotMadeleine, and it can also be used as
|
18
|
+
# persister for AutomaticSnapshotMadeleine. (One difference: the log isn't
|
19
|
+
# visible on disk until any commands are executed.)
|
20
|
+
#
|
21
|
+
# You can also use the execute_query method for shared synchronzied queries,
|
22
|
+
# for eaay coarse-grained locking of the system.
|
23
|
+
#
|
24
|
+
# The exclusive lock is only locked during the actual execution of commands and
|
25
|
+
# while closing.
|
26
|
+
#
|
27
|
+
# Keeping both log writes and executes of commands in the originating thread
|
28
|
+
# is needed by AutomaticSnapshotPrevayler. Hence the strange SimplisticPipe
|
29
|
+
# class.
|
30
|
+
#
|
31
|
+
# Todo:
|
32
|
+
# - It seems like Sync (sync.rb) prefers shared locks. This should probably
|
33
|
+
# be changed.
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# Madeleine - Ruby Object Prevalence
|
37
|
+
#
|
38
|
+
# Copyright(c) Anders Bengtsson 2003
|
39
|
+
#
|
40
|
+
|
41
|
+
require 'madeleine'
|
42
|
+
require 'madeleine/clock'
|
43
|
+
|
44
|
+
include Madeleine::Clock
|
45
|
+
|
46
|
+
module Madeleine
|
47
|
+
module Batch
|
48
|
+
class BatchedSnapshotMadeleine < SnapshotMadeleine
|
49
|
+
|
50
|
+
def initialize(directory_name, marshaller=Marshal, &new_system_block)
|
51
|
+
super(directory_name, marshaller, &new_system_block)
|
52
|
+
@log_actor = LogActor.launch(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute_command(command)
|
56
|
+
verify_command_sane(command)
|
57
|
+
queued_command = QueuedCommand.new(command)
|
58
|
+
@lock.synchronize(:SH) do
|
59
|
+
raise "closed" if @closed
|
60
|
+
@logger.store(queued_command)
|
61
|
+
end
|
62
|
+
queued_command.wait_for
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute_query(query)
|
66
|
+
verify_command_sane(query)
|
67
|
+
@lock.synchronize(:SH) do
|
68
|
+
execute_without_storing(query)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def close
|
73
|
+
@log_actor.destroy
|
74
|
+
@lock.synchronize do
|
75
|
+
@logger.close
|
76
|
+
@closed = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def flush
|
81
|
+
@lock.synchronize do
|
82
|
+
@logger.flush
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def take_snapshot
|
87
|
+
@lock.synchronize(:SH) do
|
88
|
+
@lock.synchronize do
|
89
|
+
@logger.close
|
90
|
+
end
|
91
|
+
Snapshot.new(@directory_name, system, @marshaller).take
|
92
|
+
@logger.reset
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def create_lock
|
99
|
+
Sync.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_logger(directory_name, log_factory)
|
103
|
+
BatchedLogger.new(directory_name, log_factory, self.system)
|
104
|
+
end
|
105
|
+
|
106
|
+
def log_factory
|
107
|
+
BatchedLogFactory.new
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
class LogActor
|
114
|
+
def self.launch(madeleine, delay=0.01)
|
115
|
+
result = new(madeleine, delay)
|
116
|
+
result
|
117
|
+
end
|
118
|
+
|
119
|
+
def destroy
|
120
|
+
@is_destroyed = true
|
121
|
+
if @thread.alive?
|
122
|
+
@thread.wakeup
|
123
|
+
@thread.join
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def initialize(madeleine, delay)
|
130
|
+
@is_destroyed = false
|
131
|
+
|
132
|
+
madeleine.flush
|
133
|
+
@thread = Thread.new {
|
134
|
+
until @is_destroyed
|
135
|
+
sleep(delay)
|
136
|
+
madeleine.flush
|
137
|
+
end
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class BatchedLogFactory
|
143
|
+
def create_log(directory_name)
|
144
|
+
BatchedLog.new(directory_name)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class BatchedLogger < Logger
|
149
|
+
def initialize(directory_name, log_factory, system)
|
150
|
+
super(directory_name, log_factory)
|
151
|
+
@buffer = []
|
152
|
+
@system = system
|
153
|
+
end
|
154
|
+
|
155
|
+
def store(queued_command)
|
156
|
+
@buffer << queued_command
|
157
|
+
end
|
158
|
+
|
159
|
+
def close
|
160
|
+
return if @log.nil?
|
161
|
+
flush
|
162
|
+
@log.close
|
163
|
+
@log = nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def flush
|
167
|
+
return if @buffer.empty?
|
168
|
+
|
169
|
+
open_new_log if @log.nil?
|
170
|
+
|
171
|
+
if @system.kind_of?(ClockedSystem)
|
172
|
+
@buffer.unshift(QueuedTick.new)
|
173
|
+
end
|
174
|
+
|
175
|
+
@buffer.each do |queued_command|
|
176
|
+
queued_command.store(@log)
|
177
|
+
end
|
178
|
+
|
179
|
+
@log.flush
|
180
|
+
|
181
|
+
@buffer.each do |queued_command|
|
182
|
+
queued_command.execute(@system)
|
183
|
+
end
|
184
|
+
|
185
|
+
@buffer.clear
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class BatchedLog < CommandLog
|
190
|
+
def store(command)
|
191
|
+
Marshal.dump(command, @file)
|
192
|
+
end
|
193
|
+
|
194
|
+
def flush
|
195
|
+
@file.flush
|
196
|
+
@file.fsync
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class QueuedCommand
|
201
|
+
def initialize(command)
|
202
|
+
@command = command
|
203
|
+
@pipe = SimplisticPipe.new
|
204
|
+
end
|
205
|
+
|
206
|
+
def store(log)
|
207
|
+
@pipe.write(log)
|
208
|
+
end
|
209
|
+
|
210
|
+
def execute(system)
|
211
|
+
@pipe.write(system)
|
212
|
+
end
|
213
|
+
|
214
|
+
def wait_for
|
215
|
+
@pipe.read do |log|
|
216
|
+
log.store(@command)
|
217
|
+
end
|
218
|
+
|
219
|
+
@pipe.read do |system|
|
220
|
+
return @command.execute(system)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class QueuedTick
|
226
|
+
def initialize
|
227
|
+
@tick = Tick.new(Time.now)
|
228
|
+
end
|
229
|
+
|
230
|
+
def store(log)
|
231
|
+
log.store(@tick)
|
232
|
+
end
|
233
|
+
|
234
|
+
def execute(system)
|
235
|
+
@tick.execute(system)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class SimplisticPipe
|
240
|
+
def initialize
|
241
|
+
@receive_lock = Mutex.new.lock
|
242
|
+
@consume_lock = Mutex.new.lock
|
243
|
+
@message = nil
|
244
|
+
end
|
245
|
+
|
246
|
+
def read
|
247
|
+
begin
|
248
|
+
wait_for_message_received
|
249
|
+
|
250
|
+
if block_given?
|
251
|
+
yield @message
|
252
|
+
else
|
253
|
+
return @message
|
254
|
+
end
|
255
|
+
|
256
|
+
ensure
|
257
|
+
message_consumed
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def write(message)
|
262
|
+
raise WriteBlockedException unless can_write?
|
263
|
+
|
264
|
+
@message = message
|
265
|
+
message_received
|
266
|
+
wait_for_message_consumed
|
267
|
+
@message = nil
|
268
|
+
end
|
269
|
+
|
270
|
+
def can_write?
|
271
|
+
@message.nil?
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
def message_received
|
277
|
+
@receive_lock.unlock
|
278
|
+
end
|
279
|
+
|
280
|
+
def wait_for_message_received
|
281
|
+
@receive_lock.lock
|
282
|
+
end
|
283
|
+
|
284
|
+
def message_consumed
|
285
|
+
@consume_lock.unlock
|
286
|
+
end
|
287
|
+
|
288
|
+
def wait_for_message_consumed
|
289
|
+
@consume_lock.lock
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class WriteBlockedException < Exception
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
BatchedSnapshotMadeleine = Madeleine::Batch::BatchedSnapshotMadeleine
|