Class: Forkr

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(forklet, num_kids = 1) ⇒ Forkr

Returns a new instance of Forkr.



4
5
6
7
8
9
10
# File 'lib/forkr.rb', line 4

def initialize(forklet, num_kids = 1)
  @worker_client = forklet 
  @master_pid = $$
  @children = []
  @child_count = num_kids
  @in_shutdown = false
end

Instance Attribute Details

#child_countObject (readonly)

Returns the value of attribute child_count.



2
3
4
# File 'lib/forkr.rb', line 2

def child_count
  @child_count
end

#childrenObject (readonly)

Returns the value of attribute children.



2
3
4
# File 'lib/forkr.rb', line 2

def children
  @children
end

#inboundObject (readonly)

Returns the value of attribute inbound.



2
3
4
# File 'lib/forkr.rb', line 2

def inbound
  @inbound
end

#master_pidObject (readonly)

Returns the value of attribute master_pid.



2
3
4
# File 'lib/forkr.rb', line 2

def master_pid
  @master_pid
end

#outboundObject (readonly)

Returns the value of attribute outbound.



2
3
4
# File 'lib/forkr.rb', line 2

def outbound
  @outbound
end

Instance Method Details

#add_workerObject



33
34
35
# File 'lib/forkr.rb', line 33

def add_worker
  send_wake_notice("+")
end

#child_dead?(pid) ⇒ Boolean

Returns:

  • (Boolean)


148
149
150
151
152
153
154
# File 'lib/forkr.rb', line 148

def child_dead?(pid)
  status = Process.waitpid(pid, Process::WNOHANG)
  unless status.nil?
    $stderr.puts "Process #{pid} dead: #{status}"
  end
  !status.nil?
end

#core_dump_quitObject



29
30
31
# File 'lib/forkr.rb', line 29

def core_dump_quit
  send_wake_notice("Q")
end

#dead_childObject



49
50
51
# File 'lib/forkr.rb', line 49

def dead_child
  send_wake_notice("D")
end

#decrement_workersObject



65
66
67
68
69
# File 'lib/forkr.rb', line 65

def decrement_workers
  if @child_count > 1
    @child_count = @child_count - 1
  end
end

#ensure_right_worker_countObject



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/forkr.rb', line 111

def ensure_right_worker_count
  existing_workers = @children.length
  off_by = @child_count - @children.length
  if off_by > 0
    off_by.times do
      spawn_worker
    end
  elsif off_by < 0
    @children.take(off_by.abs).each do |kid|
      signal_worker(kid, :TERM)
    end
  end
end

#increment_workersObject



61
62
63
# File 'lib/forkr.rb', line 61

def increment_workers
  @child_count = @child_count + 1
end

#interruptObject



41
42
43
# File 'lib/forkr.rb', line 41

def interrupt
  send_wake_notice("I")
end

#master_loopObject



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
# File 'lib/forkr.rb', line 77

def master_loop
  ensure_right_worker_count
  loop do
    fds = IO.select([@inbound],nil,nil,2)
    unless fds.nil?
      data_read = fds.first.first.read(1)
      if data_read == "I"
        shutdown_using(:INT)
      elsif data_read == "T"
        shutdown_using(:TERM)
      elsif data_read == "Q"
        shutdown_using(:QUIT)
      elsif data_read == "+"
        increment_workers
      elsif data_read == "-"
        decrement_workers
      end
    end
    prune_workers
    ensure_right_worker_count
  end
  reap_all_workers
  @outbound.close
  @inbound.close
end

#prune_workersObject



136
137
138
# File 'lib/forkr.rb', line 136

def prune_workers
  @children = @children.reject { |pid| child_dead?(pid) }
end

#reap_all_workersObject



103
104
105
106
107
108
109
# File 'lib/forkr.rb', line 103

def reap_all_workers
  begin
    wpid, status = Process.waitpid2(-1, Process::WNOHANG)
  rescue Errno::ECHILD
    break
  end while true
end

#remove_workerObject



37
38
39
# File 'lib/forkr.rb', line 37

def remove_worker
  send_wake_notice("-")
end

#runObject



12
13
14
15
16
17
18
19
20
21
# File 'lib/forkr.rb', line 12

def run
  @inbound, @outbound = IO.pipe
  Signal.trap('CHLD') { dead_child }
  Signal.trap('INT') { interrupt }
  Signal.trap('TERM') { shutdown }
  Signal.trap('QUIT') { core_dump_quit }
  Signal.trap('TTIN') { add_worker }
  Signal.trap('TTOU') { remove_worker }
  master_loop
end

#send_wake_notice(notice) ⇒ Object



23
24
25
26
27
# File 'lib/forkr.rb', line 23

def send_wake_notice(notice)
  return(nil) if $$ != master_pid
  return(nil) if @in_shutdown
  @outbound.write(notice)
end

#shutdownObject



45
46
47
# File 'lib/forkr.rb', line 45

def shutdown
  send_wake_notice("T")
end

#shutdown_using(sig) ⇒ Object

Raises:

  • (StopIteration)


71
72
73
74
75
# File 'lib/forkr.rb', line 71

def shutdown_using(sig)
  @in_shutdown = true
  signal_all_workers(sig)
  raise StopIteration.new
end

#signal_all_workers(sig) ⇒ Object



125
126
127
# File 'lib/forkr.rb', line 125

def signal_all_workers(sig)
  @children.each { |c| signal_worker(c, sig) }
end

#signal_worker(wpid, signal) ⇒ Object



129
130
131
132
133
134
# File 'lib/forkr.rb', line 129

def signal_worker(wpid, signal)
  begin
    Process.kill(signal, wpid)
  rescue Errno::ESRCH
  end
end

#spawn_workerObject



53
54
55
56
57
58
59
# File 'lib/forkr.rb', line 53

def spawn_worker
  if new_pid = fork
    @children << new_pid
  else
    worker_loop
  end
end

#worker_loopObject



140
141
142
143
144
145
146
# File 'lib/forkr.rb', line 140

def worker_loop
  @worker_client.after_fork if @worker_client.respond_to?(:after_fork)
  @inbound.close
  @outbound.close
  $stderr.puts "Worker spawned as #{$$}!"
  @worker_client.run
end