libraries/madeleine/automatic.rb in Pimki-1.3.092 vs libraries/madeleine/automatic.rb in Pimki-1.4.092

- old
+ new

@@ -1,357 +1,418 @@ -require 'yaml' - -module Madeleine - -# Automatic commands for Madeleine -# -# Author:: Stephen Sykes <ruby@stephensykes.com> -# Copyright:: Copyright (C) 2003-2004 -# Version:: 0.4 -# -# This module provides a way of automatically generating command objects for madeleine to -# store. It works by making a proxy object for all objects of any classes in which it is included. -# Method calls to these objects are intercepted, and stored as a command before being -# passed on to the real receiver. The command objects remember which object the command was -# destined for by using a pair of internal ids that are contained in each of the proxy objects. -# -# There is also a mechanism for specifying which methods not to intercept calls to by using -# automatic_read_only, and its opposite automatic_read_write. -# -# Should you require it, the snapshots can be stored as yaml, just pass YAML as the second -# argument to AutomaticSnapshotMadeleine.new. The system will read either Marshal or YAML at -# startup. -# -# This module is designed to work correctly in the case there are multiple madeleine systems in use by -# a single program, and is also safe to use with threads. -# -# Usage: -# -# require 'madeleine' -# require 'madeleine/automatic' -# -# class A -# include Madeleine::Automatic::Interceptor -# attr_reader :foo -# automatic_read_only :foo -# def initialize(param1, ...) -# ... -# end -# def some_method(paramA, ...) -# ... -# end -# automatic_read_only -# def bigfoo -# foo.upcase -# end -# end -# -# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } -# -# mad.system.some_method(paramA, ...) # logged as a command by madeleine -# print mad.foo # not logged -# print mad.bigfoo # not logged -# mad.take_snapshot -# - - module Automatic -# -# This module should be included (at the top) in any classes that are to be persisted. -# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine. -# It does this by returning a Prox object that is a proxy for the real object. -# -# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods -# should be made into commands -# - module Interceptor -# -# When included, redefine new so that we can return a Prox object instead, and define methods to handle -# keeping track of which methods are read only -# - def self.included(klass) - class << klass #:nodoc: - alias_method :_old_new, :new - @@auto_read_only_flag = false - @@read_only_methods = [] - - def new(*args, &block) - Prox.new(_old_new(*args, &block)) - end -# -# Called when a method added - remember symbol if read only -# - def method_added(symbol) - @@read_only_methods << symbol if @@auto_read_only_flag - end -# -# Set the read only flag, or add read only methods -# - def automatic_read_only(*list) - if (list == []) - @@auto_read_only_flag = true - else - list.each {|s| @@read_only_methods << s} - end - end -# -# Clear the read only flag, or remove read only methods -# - def automatic_read_write(*list) - if (list == []) - @@auto_read_only_flag = false - else - list.each {|s| @@read_only_methods.delete(s)} - end - end - - end - end -# -# Return the list of read only methods so Prox#method_missing can find what to and what not to make into a command -# - def read_only_methods - @@read_only_methods - end - end - -# -# A Command object is automatically created for each method call to an object within the system that comes from without. -# These objects are recorded in the log by Madeleine. -# -# Note: The command also records which system it belongs to. This is used in a recovery situation. -# If a command contains a sysid that doesn't match the system sent to us, then we change that -# system's id to the one in the command. This makes a system adopt the correct id as soon as a -# command for it is executed. This is the case when restoring a system for which there is no snapshot. -# - class Command - def initialize(symbol, myid, sysid, *args) - @symbol = symbol - @myid = myid - @sysid = sysid - @args = args - end -# -# Called by madeleine when the command is done either first time, or when restoring the log -# - def execute(system) - AutomaticSnapshotMadeleine.register_sysid(@sysid) if (system.sysid != @sysid) - Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args) - end - end -# -# This is a little class to pass to SnapshotMadeleine. This is used for snapshots only. -# It acts as the marshaller, and just passes marshalling requests on to the user specified -# marshaller. This defaults to Marshal, but could be YAML or another. -# After we have done a restore, the ObjectSpace is searched for instances of Prox to -# add new objects to the list in AutomaticSnapshotMadeleine -# - class Automatic_marshaller - def Automatic_marshaller.load(io) -# restored_obj = Thread.current[:system].marshaller.load(arg) - restored_obj = Deserialize.load(io, Thread.current[:system].marshaller) - ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)} - restored_obj - end - def Automatic_marshaller.dump(obj, stream = nil) - Thread.current[:system].marshaller.dump(obj, stream) - end - end -# -# A Prox object is generated and returned by Interceptor each time a system object is created. -# - class Prox - attr_accessor :thing, :myid, :sysid - - def initialize(thing) - if (thing) - raise "App object created outside of app" unless Thread.current[:system] - @sysid = Thread.current[:system].sysid - @myid = Thread.current[:system].add(self) - @thing = thing - end - end -# -# This automatically makes and executes a new Command if a method is called from -# outside the system. -# - def method_missing(symbol, *args, &block) -# print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n" - raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol) - if (Thread.current[:system] || @thing.read_only_methods.include?(symbol)) - @thing.send(symbol, *args, &block) - else - raise "Cannot make command with block" if block_given? - Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid] - begin - result = Thread.current[:system].execute_command(Command.new(symbol, @myid, @sysid, *args)) - ensure - Thread.current[:system] = false - end - result - end - end -# -# Custom marshalling - this adds the internal id (myid) and the system id to a marshall -# of the object we are the proxy for. -# We take care to not marshal the same object twice, so circular references will work. -# We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and -# marshal is always used for Command objects -# - def _dump(depth) - if (Thread.current[:snapshot_memory]) - if (Thread.current[:snapshot_memory][self]) - [@myid.to_s, @sysid].pack("A8A30") - else - Thread.current[:snapshot_memory][self] = true - [@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth) - end - else - [@myid.to_s, @sysid].pack("A8A30") - end - end -# -# Custom marshalling for Marshal - restore a Prox object. -# - def Prox._load(str) - x = Prox.new(nil) - a = str.unpack("A8A30a*") - x.myid = a[0].to_i - x.sysid = a[1] - x = Thread.current[:system].restore(x) - x.thing = Marshal.load(a[2]) if (a[2] > "") - x - end - - end - -# -# The AutomaticSnapshotMadeleine class contains an instance of the persister -# (default is SnapshotMadeleine) and provides additional automatic functionality. -# -# The class keeps a record of all the systems that currently exist. -# Each instance of the class keeps a record of Prox objects in that system by internal id (myid). -# -# We also add functionality to take_snapshot in order to set things up so that the custom Prox object -# marshalling will work correctly. -# - class AutomaticSnapshotMadeleine - attr_accessor :sysid, :marshaller - attr_reader :list, :marshaller - - def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block) - @sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid - @myid_count = 0 # This sysid will be used only if new - @list = {} # object is taken by madeleine - Thread.current[:system] = self # during system startup system should not create commands - AutomaticSnapshotMadeleine.register_sysid(@sysid) # this sysid may be overridden - @marshaller = marshaller # until attrb - begin - @persister = persister.new(directory_name, Automatic_marshaller, &new_system_block) - AutomaticSnapshotMadeleine.register_sysid(@sysid) # needed if there were no commands - ensure - Thread.current[:system] = false - end - end -# -# Add a proxy object to the list, return the myid for that object -# - def add(proxo) - @list[@myid_count += 1] = proxo.object_id - @myid_count - end -# -# Restore a marshalled proxy object to list - myid_count is increased as required. -# If the object already exists in the system then the existing object must be used. -# - def restore(proxo) - if (@list[proxo.myid])# && proxo.sysid == myid2ref(proxo.myid).sysid) - proxo = myid2ref(proxo.myid) - else - @list[proxo.myid] = proxo.object_id - @myid_count = proxo.myid if (@myid_count < proxo.myid) - end - @sysid = proxo.sysid # to be sure to have the correct sysid - proxo - end -# -# Returns a reference to the object indicated by the internal id supplied. -# - def myid2ref(myid) - raise "Internal id #{myid} not found" unless objid = @list[myid] - ObjectSpace._id2ref(objid) - end -# -# Take a snapshot of the system. -# - def take_snapshot - begin - Thread.current[:system] = self - Thread.current[:snapshot_memory] = {} - @persister.take_snapshot - ensure - Thread.current[:snapshot_memory] = nil - Thread.current[:system] = false - end - end -# -# Sets the real sid for this thread's system - called during startup or from a command. -# - def AutomaticSnapshotMadeleine.register_sysid(sid) - Thread.critical = true - @@systems ||= {} # holds systems by sysid - @@systems[sid] = Thread.current[:system] - Thread.critical = false - @@systems[sid].sysid = sid - @@systems[sid].list.delete_if {|k,v| # set all the prox objects that already exist to have the right sysid - begin - ObjectSpace._id2ref(v).sysid = sid - false - rescue RangeError - true # Id was to a GC'd object, delete it - end - } - end -# -# Returns the hash containing the systems. -# - def AutomaticSnapshotMadeleine.systems - @@systems - end -# -# Pass on any other calls to the persister -# - def method_missing(symbol, *args, &block) - @persister.send(symbol, *args, &block) - end - end - - - class Deserialize -# -# Detect marshal format, and return deserialized object using the right marshaller -# If detection didn't work, use the marshaller given in the optional 2nd argument -# - def Deserialize.load(io, marshaller=Marshal) - c = io.getc - c1 = io.getc - io.rewind - if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION) - Marshal.load(io) - else - while (s = io.gets) - break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines - end - io.rewind - if (s && s =~ /^\s*---/) # "---" is the yaml header - YAML.load(io) - else - marshaller.load(io) - end - end - end - - end - - end -end - -AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine +require 'yaml' +require 'madeleine/zmarshal' +require 'soap/marshal' + +module Madeleine + +# Automatic commands for Madeleine +# +# Author:: Stephen Sykes <sds@stephensykes.com> +# Copyright:: Copyright (C) 2003-2004 +# Version:: 0.41 +# +# This module provides a way of automatically generating command objects for madeleine to +# store. It works by making a proxy object for all objects of any classes in which it is included. +# Method calls to these objects are intercepted, and stored as a command before being +# passed on to the real receiver. The command objects remember which object the command was +# destined for by using a pair of internal ids that are contained in each of the proxy objects. +# +# There is also a mechanism for specifying which methods not to intercept calls to by using +# automatic_read_only, and its opposite automatic_read_write. +# +# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass +# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new. +# If the passed marshaller did not successfully deserialize the latest snapshot, the system +# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding +# compressed versions. +# +# This module is designed to work correctly in the case there are multiple madeleine systems in use by +# a single program, and is also safe to use with threads. +# +# Usage: +# +# require 'madeleine' +# require 'madeleine/automatic' +# +# class A +# include Madeleine::Automatic::Interceptor +# attr_reader :foo +# automatic_read_only :foo +# def initialize(param1, ...) +# ... +# end +# def some_method(paramA, ...) +# ... +# end +# automatic_read_only +# def bigfoo +# foo.upcase +# end +# end +# +# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } +# +# mad.system.some_method(paramA, ...) # logged as a command by madeleine +# print mad.foo # not logged +# print mad.bigfoo # not logged +# mad.take_snapshot +# + + module Automatic +# +# This module should be included (at the top) in any classes that are to be persisted. +# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine. +# It does this by returning a Prox object that is a proxy for the real object. +# +# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods +# should be made into commands +# + module Interceptor +# +# When included, redefine new so that we can return a Prox object instead, and define methods to handle +# keeping track of which methods are read only +# + def self.included(klass) + class <<klass + alias_method :_old_new, :new + + def new(*args, &block) + Prox.new(_old_new(*args, &block)) + end +# +# Called when a method added - remember symbol if read only +# This is a good place to add in any superclass's read only methods also +# + def method_added(symbol) + self.instance_eval { + @read_only_methods ||= [] + @auto_read_only_flag ||= false + @read_only_methods << symbol if @auto_read_only_flag + c = self + while (c = c.superclass) + if (c.instance_eval {instance_variables.include? "@read_only_methods"}) + @read_only_methods |= c.instance_eval {@read_only_methods} + end + end + } + end +# +# Set the read only flag, or add read only methods +# + def automatic_read_only(*list) + if (list == []) + self.instance_eval {@auto_read_only_flag = true} + else + list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods << s}} + end + end +# +# Clear the read only flag, or remove read only methods +# + def automatic_read_write(*list) + if (list == []) + self.instance_eval {@auto_read_only_flag = false} + else + list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods.delete(s)}} + end + end + + end + end +# +# Return the list of read only methods so Automatic_proxy#method_missing can find what to and what not to make into a command +# + def read_only_methods + self.class.instance_eval {@read_only_methods} + end + end + +# +# A Command object is automatically created for each method call to an object within the system that comes from without. +# These objects are recorded in the log by Madeleine. +# + class Command + def initialize(symbol, myid, *args) + @symbol = symbol + @myid = myid + @args = args + end +# +# Called by madeleine when the command is done either first time, or when restoring the log +# + def execute(system) + Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args) + end + end +# +# This is a little class to pass to SnapshotMadeleine. This is used for snapshots only. +# It acts as the marshaller, and just passes marshalling requests on to the user specified +# marshaller. This defaults to Marshal, but could be YAML or another. +# After we have done a restore, the ObjectSpace is searched for instances of Prox to +# add new objects to the list in AutomaticSnapshotMadeleine +# + class Automatic_marshaller #:nodoc: + def Automatic_marshaller.load(io) + restored_obj = Deserialize.load(io, Thread.current[:system].marshaller) + ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)} + restored_obj + end + def Automatic_marshaller.dump(obj, io = nil) + Thread.current[:system].marshaller.dump(obj, io) + end + end +# +# A Prox object is generated and returned by Interceptor each time a system object is created. +# + class Prox #:nodoc: + attr_accessor :thing, :myid, :sysid + + def initialize(thing) + if (thing) + raise "App object created outside of app" unless Thread.current[:system] + @sysid = Thread.current[:system].sysid + @myid = Thread.current[:system].add(self) + @thing = thing + end + end +# +# This automatically makes and executes a new Command if a method is called from +# outside the system. +# + def method_missing(symbol, *args, &block) +# print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n" + raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol) + if (Thread.current[:system]) + @thing.send(symbol, *args, &block) + else + raise "Cannot make command with block" if block_given? + Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid] + begin + if (@thing.read_only_methods.include?(symbol)) + result = Thread.current[:system].execute_query(Command.new(symbol, @myid, *args)) + else + result = Thread.current[:system].execute_command(Command.new(symbol, @myid, *args)) + end + ensure + Thread.current[:system] = false + end + result + end + end +# +# Custom marshalling - this adds the internal id (myid) and the system id to a marshal +# of the object we are the proxy for. +# We take care to not marshal the same object twice, so circular references will work. +# We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and +# marshal is always used for Command objects +# + def _dump(depth) + if (Thread.current[:snapshot_memory]) + if (Thread.current[:snapshot_memory][self]) + [@myid.to_s, @sysid].pack("A8A30") + else + Thread.current[:snapshot_memory][self] = true + [@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth) + end + else + [@myid.to_s, @sysid].pack("A8A30") # never marshal a prox object in a command, just ref + end + end + +# +# Custom marshalling for Marshal - restore a Prox object. +# + def Prox._load(str) + x = Prox.new(nil) + a = str.unpack("A8A30a*") + x.myid = a[0].to_i + x.sysid = a[1] + x = Thread.current[:system].restore(x) + x.thing = Marshal.load(a[2]) if (a[2] > "") + x + end + + end + +# +# The AutomaticSnapshotMadeleine class contains an instance of the persister +# (default is SnapshotMadeleine) and provides additional automatic functionality. +# +# The class is instantiated the same way as SnapshotMadeleine: +# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } +# The second initialisation parameter is the persister. Supported persisters are: +# +# * Marshal (default) +# * YAML +# * SOAP::Marshal +# * Madeleine::ZMarshal.new(Marshal) +# * Madeleine::ZMarshal.new(YAML) +# * Madeleine::ZMarshal.new(SOAP::Marshal) +# +# The class keeps a record of all the systems that currently exist. +# Each instance of the class keeps a record of Prox objects in that system by internal id (myid). +# +# We also add functionality to take_snapshot in order to set things up so that the custom Prox object +# marshalling will work correctly. +# + class AutomaticSnapshotMadeleine + attr_accessor :marshaller + attr_reader :list, :sysid + + def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block) + @sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid + @myid_count = 0 + @list = {} + Thread.current[:system] = self # during system startup system should not create commands + Thread.critical = true + @@systems ||= {} # holds systems by sysid + @@systems[@sysid] = self + Thread.critical = false + @marshaller = marshaller # until attrb + begin + @persister = persister.new(directory_name, Automatic_marshaller, &new_system_block) + @list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid + begin + ObjectSpace._id2ref(v).sysid = @sysid + false + rescue RangeError + true # Id was to a GC'd object, delete it + end + } + ensure + Thread.current[:system] = false + end + end +# +# Add a proxy object to the list, return the myid for that object +# + def add(proxo) + @list[@myid_count += 1] = proxo.object_id + @myid_count + end +# +# Restore a marshalled proxy object to list - myid_count is increased as required. +# If the object already exists in the system then the existing object must be used. +# + def restore(proxo) + if (@list[proxo.myid]) + proxo = myid2ref(proxo.myid) + else + @list[proxo.myid] = proxo.object_id + @myid_count = proxo.myid if (@myid_count < proxo.myid) + end + proxo + end +# +# Returns a reference to the object indicated by the internal id supplied. +# + def myid2ref(myid) + raise "Internal id #{myid} not found" unless objid = @list[myid] + ObjectSpace._id2ref(objid) + end +# +# Take a snapshot of the system. +# + def take_snapshot + begin + Thread.current[:system] = self + Thread.current[:snapshot_memory] = {} + @persister.take_snapshot + ensure + Thread.current[:snapshot_memory] = nil + Thread.current[:system] = false + end + end +# +# Returns the hash containing the systems. +# + def AutomaticSnapshotMadeleine.systems + @@systems + end +# +# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new +# system before GC gets them +# + def close + begin + @list.each_key {|k| myid2ref(k).sysid = nil} + rescue RangeError + # do nothing + end + @persister.close + end + +# +# Pass on any other calls to the persister +# + def method_missing(symbol, *args, &block) + @persister.send(symbol, *args, &block) + end + end + + + module Deserialize #:nodoc: +# +# Detect format of an io stream. Leave it rewound. +# + def Deserialize.detect(io) + c = io.getc + c1 = io.getc + io.rewind + if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION) + Marshal + elsif (c == 31 && c1 == 139) # gzip magic numbers + ZMarshal + else + while (s = io.gets) + break if (s !~ /^\s*$/) # ignore blank lines + end + io.rewind + if (s && s =~ /^\s*<\?[xX][mM][lL]/) # "<?xml" begins an xml serialization + SOAP::Marshal + else + while (s = io.gets) + break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines + end + io.rewind + if (s && s =~ /^\s*---/) # "---" is the yaml header + YAML + else + nil # failed to detect + end + end + end + end +# +# Try to deserialize object. If there was an error, try to detect marshal format, +# and return deserialized object using the right marshaller +# If detection didn't work, raise up the exception +# + def Deserialize.load(io, marshaller=Marshal) + begin + marshaller.load(io) + rescue Exception => e + io.rewind + detected_marshaller = detect(io) + if (detected_marshaller == ZMarshal) + zio = Zlib::GzipReader.new(io) + detected_zmarshaller = detect(zio) + zio.finish + io.rewind + if (detected_zmarshaller) + ZMarshal.new(detected_zmarshaller).load(io) + else + raise e + end + elsif (detected_marshaller) + detected_marshaller.load(io) + else + raise e + end + end + end + end + + end +end + +AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine