1require 'drb/drb'
2require 'monitor'
3
4module DRb
5
6  # Timer id conversion keeps objects alive for a certain amount of time after
7  # their last access.  The default time period is 600 seconds and can be
8  # changed upon initialization.
9  #
10  # To use TimerIdConv:
11  #
12  #  DRb.install_id_conv TimerIdConv.new 60 # one minute
13
14  class TimerIdConv < DRbIdConv
15    class TimerHolder2 # :nodoc:
16      include MonitorMixin
17
18      class InvalidIndexError < RuntimeError; end
19
20      def initialize(timeout=600)
21        super()
22        @sentinel = Object.new
23        @gc = {}
24        @curr = {}
25        @renew = {}
26        @timeout = timeout
27        @keeper = keeper
28      end
29
30      def add(obj)
31        synchronize do
32          key = obj.__id__
33          @curr[key] = obj
34          return key
35        end
36      end
37
38      def fetch(key, dv=@sentinel)
39        synchronize do
40          obj = peek(key)
41          if obj == @sentinel
42            return dv unless dv == @sentinel
43            raise InvalidIndexError
44          end
45          @renew[key] = obj # KeepIt
46          return obj
47        end
48      end
49
50      def include?(key)
51        synchronize do
52          obj = peek(key)
53          return false if obj == @sentinel
54          true
55        end
56      end
57
58      def peek(key)
59        synchronize do
60          return @curr.fetch(key, @renew.fetch(key, @gc.fetch(key, @sentinel)))
61        end
62      end
63
64      private
65      def alternate
66        synchronize do
67          @gc = @curr       # GCed
68          @curr = @renew
69          @renew = {}
70        end
71      end
72
73      def keeper
74        Thread.new do
75          loop do
76            alternate
77            sleep(@timeout)
78          end
79        end
80      end
81    end
82
83    # Creates a new TimerIdConv which will hold objects for +timeout+ seconds.
84    def initialize(timeout=600)
85      @holder = TimerHolder2.new(timeout)
86    end
87
88    def to_obj(ref) # :nodoc:
89      return super if ref.nil?
90      @holder.fetch(ref)
91    rescue TimerHolder2::InvalidIndexError
92      raise "invalid reference"
93    end
94
95    def to_id(obj) # :nodoc:
96      return @holder.add(obj)
97    end
98  end
99end
100
101# DRb.install_id_conv(TimerIdConv.new)
102