1module Rake
2
3  # A Promise object represents a promise to do work (a chore) in the
4  # future. The promise is created with a block and a list of
5  # arguments for the block. Calling value will return the value of
6  # the promised chore.
7  #
8  # Used by ThreadPool.
9  #
10  class Promise               # :nodoc: all
11    NOT_SET = Object.new.freeze # :nodoc:
12
13    attr_accessor :recorder
14
15    # Create a promise to do the chore specified by the block.
16    def initialize(args, &block)
17      @mutex = Mutex.new
18      @result = NOT_SET
19      @error = NOT_SET
20      @args = args.collect { |a| begin; a.dup; rescue; a; end }
21      @block = block
22    end
23
24    # Return the value of this promise.
25    #
26    # If the promised chore is not yet complete, then do the work
27    # synchronously. We will wait.
28    def value
29      unless complete?
30        stat :sleeping_on, :item_id => object_id
31        @mutex.synchronize do
32          stat :has_lock_on, :item_id => object_id
33          chore
34          stat :releasing_lock_on, :item_id => object_id
35        end
36      end
37      error? ? raise(@error) : @result
38    end
39
40    # If no one else is working this promise, go ahead and do the chore.
41    def work
42      stat :attempting_lock_on, :item_id => object_id
43      if @mutex.try_lock
44        stat :has_lock_on, :item_id => object_id
45        chore
46        stat :releasing_lock_on, :item_id => object_id
47        @mutex.unlock
48      else
49        stat :bailed_on, :item_id => object_id
50      end
51    end
52
53    private
54
55    # Perform the chore promised
56    def chore
57      if complete?
58        stat :found_completed, :item_id => object_id
59        return
60      end
61      stat :will_execute, :item_id => object_id
62      begin
63        @result = @block.call(*@args)
64      rescue Exception => e
65        @error = e
66      end
67      stat :did_execute, :item_id => object_id
68      discard
69    end
70
71    # Do we have a result for the promise
72    def result?
73      ! @result.equal?(NOT_SET)
74    end
75
76    # Did the promise throw an error
77    def error?
78      ! @error.equal?(NOT_SET)
79    end
80
81    # Are we done with the promise
82    def complete?
83      result? || error?
84    end
85
86    # free up these items for the GC
87    def discard
88      @args = nil
89      @block = nil
90    end
91
92    # Record execution statistics if there is a recorder
93    def stat(*args)
94      @recorder.call(*args) if @recorder
95    end
96
97  end
98
99end
100