aboutsummaryrefslogtreecommitdiff
path: root/lib/mauve/mauve_thread.rb
blob: 7d5dcbe5dff7d956c06102cd069ba39c055a6dcb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
require 'thread'
require 'singleton'

module Mauve

  class MauveThread

    attr_reader :poll_every

    def initialize
      @thread = nil
    end

    def logger
      @logger ||= Log4r::Logger.new(self.class.to_s) 
    end

    def poll_every=(i)
      raise ArgumentError.new("poll_every must be numeric") unless i.is_a?(Numeric)
      # 
      # Set the minimum poll frequency.
      #
      if i.to_f < 0
        logger.debug "Increasing thread polling interval to 0s from #{i}"
        i = 0 
      end

      @poll_every = i
    end

    def run_thread(interval = 1.0)
      #
      # Good to go.
      #
      @thread = Thread.current
      self.state = :starting

      @poll_every ||= interval

      sleep_loops = (@poll_every.to_f / 0.1).round.to_i
      sleep_loops = 1 if sleep_loops.nil? or sleep_loops < 1

      while self.state != :stopping do

        self.state = :started if self.state == :starting

        #
        # Schtop!
        #
        if self.state == :freezing
          self.state = :frozen
          Thread.stop
          self.state = :started
        end

        yield

        #
        # Ah-ha! Sleep with a break clause.
        #
        sleep_loops.times do

          break if self.should_stop?

          #
          # This is a rate-limiting step
          #
          Kernel.sleep 0.1
        end
      end

      self.state = :stopped
    end

    def should_stop?
      [:freezing, :stopping].include?(self.state)
    end

    def state
      if self.alive?
        @thread.key?(:state) ?  @thread[:state] : :unknown
      else
        :stopped
      end
    end

    def state=(s)
      raise "Bad state for mauve_thread #{s.inspect}" unless [:stopped, :starting, :started, :freezing, :frozen, :stopping, :killing, :killed].include?(s)
      raise "Thread not ready yet." unless @thread.is_a?(Thread)

      unless @thread[:state] == s
        @thread[:state] = s
        @thread[:last_state_change] = Time.now
        logger.debug(s.to_s.capitalize) 
      end

      @thread[:state]
    end

    def last_state_change
      if self.alive? and @thread.key?(:last_state_change)
        return @thread[:last_state_change]
      else
        return nil
      end
    end

    def freeze
      self.state = :freezing
      
      20.times { Kernel.sleep 0.2 ; break if @thread.stop? }

      logger.warn("Thread has not frozen!") unless @thread.stop?
    end

    def frozen?
      self.stop? and self.state == :frozen
    end

    def run
      if self.alive? 
        # Wake up if we're stopped.
        if self.stop?
          @thread.wakeup 
        end
      else
        @logger = nil
        Thread.new do
          self.run_thread { self.main_loop } 
        end
      end
    end

    alias start run
    alias thaw  run

    def alive?
      @thread.is_a?(Thread) and @thread.alive?
    end

    def stop?
      self.alive? and @thread.stop?
    end

    def join(ok_exceptions=[])
      @thread.join if @thread.is_a?(Thread)
    end

#    def raise(ex)
#      @thread.raise(ex)
#    end

    def backtrace
      @thread.backtrace if @thread.is_a?(Thread)
    end

    def restart
      self.stop
      self.start
    end
    
    def stop
      self.state = :stopping

      10.times do 
        break unless self.alive?
        Kernel.sleep 1 if self.alive? 
      end

      #
      # OK I've had enough now.
      #
      self.kill if self.alive?

      self.join 
    end

    alias exit stop

    def kill
      self.state = :killing
      @thread.kill
      self.state = :killed
    end

    def thread
      @thread
    end

  end

end