Class: QuackConcurrency::UninterruptibleSleeper

Inherits:
Object
  • Object
show all
Defined in:
lib/quack_concurrency/uninterruptible_sleeper.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(thread) ⇒ UninterruptibleSleeper

Returns a new instance of UninterruptibleSleeper.

Raises:

  • (ArgumentError)


8
9
10
11
12
13
14
15
# File 'lib/quack_concurrency/uninterruptible_sleeper.rb', line 8

def initialize(thread)
  raise ArgumentError, "'thread' must be a Thread" unless thread.is_a?(Thread)
  @thread = thread
  @state = :running
  @mutex = ::Mutex.new
  @stop_called = false
  @run_called = false
end

Class Method Details

.for_currentObject



4
5
6
# File 'lib/quack_concurrency/uninterruptible_sleeper.rb', line 4

def self.for_current
  new(Thread.current)
end

Instance Method Details

#run_threadObject



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/quack_concurrency/uninterruptible_sleeper.rb', line 17

def run_thread
  @mutex.synchronize do
    raise '#run_thread has already been called once' if @run_called
    @run_called = true
    return if @state == :running
    Thread.pass until @state = :running || @thread.status == 'sleep'
    @state = :running
    @thread.run
  end
  nil
end

#sleep_thread(duration) ⇒ Object



29
30
31
32
33
# File 'lib/quack_concurrency/uninterruptible_sleeper.rb', line 29

def sleep_thread(duration)
  start_time = Time.now
  stop_thread(timeout: duration)
  time_elapsed = Time.now - start_time
end

#stop_thread(timeout: nil) ⇒ Object



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
# File 'lib/quack_concurrency/uninterruptible_sleeper.rb', line 35

def stop_thread(timeout: nil)
  raise 'can only stop current Thread' unless Thread.current == @thread
  raise "'timeout' argument must be nil or a Numeric" if timeout != nil && !timeout.is_a?(Numeric)
  raise '#stop_thread has already been called once' if @stop_called
  @stop_called = true
  target_end_time = Time.now + timeout if timeout
  @mutex.synchronize do
    return if @run_called
    @state = :sleeping
    @mutex.unlock
    loop do
      if timeout
        time_left = target_end_time - Time.now
        Kernel.sleep(time_left) if time_left > 0
      else
        Thread.stop
      end
      break if @state == :running || Time.now >= target_time
    end
    @state = :running
    
    # we relock the mutex ensure #run_thread has finshed before #stop_thread
    # if Thread#run is called by another part of the code at the same time as
    #   #run_thread is being called, we dont want the call to #run_thread
    #   to call Thread#run on a Thread has already resumed and stopped again
    @mutex.lock
  end
  nil
end