lib/openwfe/worklist/storelocks.rb in ruote-0.9.18 vs lib/openwfe/worklist/storelocks.rb in ruote-0.9.19

- old
+ new

@@ -1,34 +1,34 @@ # #-- # Copyright (c) 2007-2008, John Mettraux, OpenWFE.org # All rights reserved. -# -# Redistribution and use in source and binary forms, with or without +# +# Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: -# +# # . Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# . Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation +# list of conditions and the following disclaimer. +# +# . Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. -# +# # . Neither the name of the "OpenWFE" nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. #++ # # @@ -43,251 +43,251 @@ require 'openwfe/contextual' module OpenWFE - # - # TODO #11162 : turn this class into a mixin - # + # + # TODO #11162 : turn this class into a mixin + # + # + # A wrapper for a Store[Participant] that includes a lock system. + # + class StoreWithLocks + include Contextual + + DEFAULT_LOCK_MAX_AGE = "1h" + + attr_accessor :lock_max_age + attr_reader :store + # - # A wrapper for a Store[Participant] that includes a lock system. + # Builds a new store with a lock system wrapping a 'real_store'. # - class StoreWithLocks - include Contextual + # This parameter 'real_store' may be a Class, like in + # + # store = StoreWithLocks.new(HashParticipant) + # + # You can retrieve the 'real store' with + # + # real_store = store.store + # + # By default, a lock is kept for one hour. You can change that + # value with, for example : + # + # store = StoreWithLocks.new(HashParticipant, :lock_max_age => "30m10s" + # + # (setting the lock maximum age to thirty minutes and 10 seconds). + # + def initialize (real_store, application_context=nil, params={}) - DEFAULT_LOCK_MAX_AGE = "1h" + @store = real_store + @store = @store.new if @store.kind_of?(Class) - attr_accessor :lock_max_age - attr_reader :store + self.application_context = application_context - # - # Builds a new store with a lock system wrapping a 'real_store'. - # - # This parameter 'real_store' may be a Class, like in - # - # store = StoreWithLocks.new(HashParticipant) - # - # You can retrieve the 'real store' with - # - # real_store = store.store - # - # By default, a lock is kept for one hour. You can change that - # value with, for example : - # - # store = StoreWithLocks.new(HashParticipant, :lock_max_age => "30m10s" - # - # (setting the lock maximum age to thirty minutes and 10 seconds). - # - def initialize (real_store, application_context=nil, params={}) + @lock_max_age = params[:lock_max_age] || DEFAULT_LOCK_MAX_AGE + @lock_max_age = Rufus::parse_time_string @lock_max_age - @store = real_store - @store = @store.new if @store.kind_of?(Class) + @locks = {} + @lock_mutex = Mutex.new + end - self.application_context = application_context + # + # Sets the application context of this store lock and of the + # real store behind. + # + def application_context= (ac) - @lock_max_age = params[:lock_max_age] || DEFAULT_LOCK_MAX_AGE - @lock_max_age = Rufus::parse_time_string @lock_max_age + @application_context = ac - @locks = {} - @lock_mutex = Mutex.new - end + if @store.respond_to?(:application_context=) and \ + not store.application_context - # - # Sets the application context of this store lock and of the - # real store behind. - # - def application_context= (ac) + @store.application_context = @application_context + end + end - @application_context = ac + # + # Get a workitem, lock it and then return it. Ensures that no other + # 'locker' can lock it meanwhile. + # + def get_and_lock (locker, key) - if @store.respond_to?(:application_context=) and \ - not store.application_context + @lock_mutex.synchronize do - @store.application_context = @application_context - end - end + object = @store[key] - # - # Get a workitem, lock it and then return it. Ensures that no other - # 'locker' can lock it meanwhile. - # - def get_and_lock (locker, key) + return nil unless object - @lock_mutex.synchronize do + not_locked?(key) - object = @store[key] + @locks[key] = [ locker, Time.now.to_i ] + object + end + end - return nil unless object + alias :lock :get_and_lock - not_locked?(key) + # + # Gets a workitem without locking it. + # + def get (key) - @locks[key] = [ locker, Time.now.to_i ] - object - end - end + @store[key] + end - alias :lock :get_and_lock + # + # Removes a lock set on an item. + # If the item was locked by some other locker, will raise an exception. + # If the item was not locked, will simply exit silently. + # + def release (locker, key) - # - # Gets a workitem without locking it. - # - def get (key) + @lock_mutex.synchronize do + holding_lock? locker, key + @locks.delete key + end + end - @store[key] - end + # + # Returns the locker currently holding a given object + # (known by its key). + # Will return nil if the object is not locked (or doesn't exist). + # + def get_locker (key) - # - # Removes a lock set on an item. - # If the item was locked by some other locker, will raise an exception. - # If the item was not locked, will simply exit silently. - # - def release (locker, key) + lock = get_lock key + return nil unless lock + lock[0] + end - @lock_mutex.synchronize do - holding_lock? locker, key - @locks.delete key - end - end + # + # Saves the workitem and releases the lock on it. + # + def save (locker, workitem) - # - # Returns the locker currently holding a given object - # (known by its key). - # Will return nil if the object is not locked (or doesn't exist). - # - def get_locker (key) + save_or_forward :save, locker, workitem + end - lock = get_lock key - return nil unless lock - lock[0] - end + # + # Forwards the workitem (to the engine) and releases the lock on + # it (of course, it's not in the store anymore). + # + def forward (locker, workitem) - # - # Saves the workitem and releases the lock on it. - # - def save (locker, workitem) + save_or_forward :forward, locker, workitem + end - save_or_forward :save, locker, workitem - end + alias :proceed :forward - # - # Forwards the workitem (to the engine) and releases the lock on - # it (of course, it's not in the store anymore). - # - def forward (locker, workitem) + # + # Directly forwards the list_workitems() call to the wrapped store. + # + def list_workitems (workflow_instance_id=nil) - save_or_forward :forward, locker, workitem - end + @store.list_workitems(workflow_instance_id) + end - alias :proceed :forward + # + # Returns the count of workitems in the store. + # + def size - # - # Directly forwards the list_workitems() call to the wrapped store. - # - def list_workitems (workflow_instance_id=nil) + @store.size + end - @store.list_workitems(workflow_instance_id) - end + # + # Just calls the consume method of the underlying store. + # + def consume (workitem) - # - # Returns the count of workitems in the store. - # - def size + @store.consume workitem + end - @store.size - end + # + # Iterates over the workitems in the store. + # + # Doesn't care about any order for now. + # + def each (&block) # :yields: workitem, locked - # - # Just calls the consume method of the underlying store. - # - def consume (workitem) + @store.each do |fei, workitem| + block.call workitem, locked?(fei) + end + end - @store.consume workitem - end + protected - # - # Iterates over the workitems in the store. - # - # Doesn't care about any order for now. - # - def each (&block) # :yields: workitem, locked + def save_or_forward (method, locker, workitem) - @store.each do |fei, workitem| - block.call workitem, locked?(fei) - end + @lock_mutex.synchronize do + holding_lock? locker, workitem.fei + @locks.delete workitem.fei + @store.send method, workitem end + end - protected + # + # Returns the lock info (else nil) for the given key. + # + def get_lock (key) - def save_or_forward (method, locker, workitem) + lock = @locks[key] - @lock_mutex.synchronize do - holding_lock? locker, workitem.fei - @locks.delete workitem.fei - @store.send method, workitem - end - end + return nil unless lock - # - # Returns the lock info (else nil) for the given key. - # - def get_lock (key) + l, lt = lock - lock = @locks[key] + if (Time.now.to_i - lt) > @lock_max_age + @locks.delete key + return nil + end - return nil unless lock + [ l, lt ] + end - l, lt = lock + # + # Returns true if the object is locked + # + def locked? (key) - if (Time.now.to_i - lt) > @lock_max_age - @locks.delete key - return nil - end + @locks[key] != nil + end - [ l, lt ] - end + # + # Will raise an exception if the object (designated via its key) + # is already locked. + # + def not_locked? (key) - # - # Returns true if the object is locked - # - def locked? (key) + raise "already locked" if get_lock key + end - @locks[key] != nil - end + # + # Will raise an exception if the locker is not holding a lock + # for the given key. + # + def holding_lock? (locker, key) - # - # Will raise an exception if the object (designated via its key) - # is already locked. - # - def not_locked? (key) + lock = get_lock key + raise "not locked" unless lock + l, lt = lock + raise "locked by someone else" if (l != locker) + # else, simply end + end - raise "already locked" if get_lock key - end - - # - # Will raise an exception if the locker is not holding a lock - # for the given key. - # - def holding_lock? (locker, key) - - lock = get_lock key - raise "not locked" unless lock - l, lt = lock - raise "locked by someone else" if (l != locker) - # else, simply end - end - - #-- - # Sets the lock on a given key to 'now'. - # - #def touch_lock (key) - # lock = @locks[key] - # return false unless lock - # locker, lock_time = lock - # @locks[key] = [ locker, Time.now.to_i ] - # true - #end - #++ - end + #-- + # Sets the lock on a given key to 'now'. + # + #def touch_lock (key) + # lock = @locks[key] + # return false unless lock + # locker, lock_time = lock + # @locks[key] = [ locker, Time.now.to_i ] + # true + #end + #++ + end end