Class: Ffmprb::Util::Thread

Inherits:
Thread
  • Object
show all
Includes:
ProcVis::Node
Defined in:
lib/ffmprb/util/thread.rb

Direct Known Subclasses

Reader

Defined Under Namespace

Classes: Error, ParentError

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes included from ProcVis::Node

#_proc_vis

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ProcVis::Node

#proc_vis_edge, #proc_vis_name, #proc_vis_node

Constructor Details

#initialize(name = "some", main: false, &blk) ⇒ Thread



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
# File 'lib/ffmprb/util/thread.rb', line 45

def initialize(name="some", main: false, &blk)
  orig_caller = caller
  @name = name
  @parent = Thread.current
  @live_children = []
  @children_mon = Monitor.new
  @dead_children_q = Queue.new
  @backtrace = (@parent.respond_to?(:backtrace)? @parent.backtrace : []) + caller
  Ffmprb.logger.debug{"about to launch #{'main ' if main}#{name}"}
  sync_q = Queue.new
  super() do
    self.report_on_exception = false
    @parent.proc_vis_node self  if @parent.respond_to? :proc_vis_node
    if @parent.respond_to? :child_lives
      @parent.child_lives self
      Ffmprb.logger.warn "Not the main: false thread run by a #{self.class.name} thread"  if main
    else
      Ffmprb.logger.warn "Not the main: true thread run by a not #{self.class.name} thread"  unless main
    end
    sync_q.enq :ok
    Ffmprb.logger.debug{"#{name} thread launched"}
    begin
      blk.call.tap do
        Ffmprb.logger.debug{"#{name} thread done"}
      end
    rescue Exception
      Ffmprb.logger.warn "#{$!.class.name} raised in #{name} thread: #{$!.message}\nBacktrace:\n\t#{($!.backtrace + backtrace ).join("\n\t")}"
      cause = $!
      Ffmprb.logger.warn "...caused by #{cause.class.name}: #{cause.message}\nBacktrace:\n\t#{cause.backtrace.join("\n\t")}" while
        cause = cause.cause
      raise $!  # TODO? I have no idea why I need to give it `$!` -- the docs say I need not
    ensure
      @parent.child_dies self  if @parent.respond_to? :child_dies
      @parent.proc_vis_node self, :remove  if @parent.respond_to? :proc_vis_node
    end
  end
  sync_q.deq
end

Class Attribute Details

.timeoutObject

Returns the value of attribute timeout.



13
14
15
# File 'lib/ffmprb/util/thread.rb', line 13

def timeout
  @timeout
end

Instance Attribute Details

#backtraceObject (readonly)

Returns the value of attribute backtrace.



43
44
45
# File 'lib/ffmprb/util/thread.rb', line 43

def backtrace
  @backtrace
end

#nameObject (readonly)

Returns the value of attribute name.



42
43
44
# File 'lib/ffmprb/util/thread.rb', line 42

def name
  @name
end

Class Method Details

.join_children!(limit = nil, timeout: self.timeout) ⇒ Object



36
37
38
# File 'lib/ffmprb/util/thread.rb', line 36

def join_children!(limit=nil, timeout: self.timeout)
  Thread.current.join_children! limit, timeout: timeout
end

.timeout_or_live(limit = nil, log: "while doing this", timeout: self.timeout, &blk) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ffmprb/util/thread.rb', line 15

def timeout_or_live(limit=nil, log: "while doing this", timeout: self.timeout, &blk)
  started_at = Time.now
  timeouts = 0
  logged_timeouts = 1
  begin
    timeouts += 1
    time = Time.now - started_at
    fail TimeLimitError  if limit && time > limit
    Timeout.timeout timeout do
      blk.call time
    end
  rescue Timeout::Error
    if timeouts > 2 * logged_timeouts
      Ffmprb.logger.info "A little bit of timeout #{log.respond_to?(:call)? log.call : log} (##{timeouts}x#{timeout})"
      logged_timeouts = timeouts
    end
    current.live!
    retry
  end
end

Instance Method Details

#child_dies(thr) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/ffmprb/util/thread.rb', line 98

def child_dies(thr)
  @children_mon.synchronize do
    Ffmprb.logger.debug{"releasing #{thr.name} thread"}
    @dead_children_q.enq thr
    fail "System Error"  unless @live_children.delete thr
  end
  proc_vis_edge self, thr, :remove
end

#child_lives(thr) ⇒ Object



90
91
92
93
94
95
96
# File 'lib/ffmprb/util/thread.rb', line 90

def child_lives(thr)
  @children_mon.synchronize do
    Ffmprb.logger.debug{"picking up #{thr.name} thread"}
    @live_children << thr
  end
  proc_vis_edge self, thr
end

#join_children!(limit = nil, timeout: Thread.timeout) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ffmprb/util/thread.rb', line 107

def join_children!(limit=nil, timeout: Thread.timeout)
  timeout = [timeout, limit].compact.min
  Ffmprb.logger.debug "joining threads: #{@live_children.size} live, #{@dead_children_q.size} dead"
  until @live_children.empty? && @dead_children_q.empty?
    thr = self.class.timeout_or_live limit, log: "joining threads: #{@live_children.size} live, #{@dead_children_q.size} dead", timeout: timeout do
      @dead_children_q.deq
    end
    Ffmprb.logger.debug "joining the late #{thr.name} thread"
    fail "System Error"  unless thr.join(timeout)  # NOTE should not block
  end
end

#live!Object

TODO protected: none of these methods should be called by a user code, the only public methods are above



86
87
88
# File 'lib/ffmprb/util/thread.rb', line 86

def live!
  fail ParentError  if @parent.status.nil?
end