parallel_server 0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d780167579eef4a691c12ab19a0ace61393a41e
4
- data.tar.gz: f6a61cfbe9f26dfb093eef85347a5d64ba706449
3
+ metadata.gz: 45b4976310bb10eafcde6ffd88c58109b37e8484
4
+ data.tar.gz: f82ca0d3995fd5ea389696d3cbf8f4db793ca6cc
5
5
  SHA512:
6
- metadata.gz: 98a37012be40e65058989a402d01058c4d6af1505d095187881dcddc94d1cceed50d5ea66b594e7131837e75dd182d8fa333c51dde2ff3edab240e10c7439768
7
- data.tar.gz: c41df8dbfdac04e5923eb110e8b10231d878c1b97a80b97925480abe95558d23e90a9c63e03b6627967432563fc976838792d46544e6f2dd7367d7de096dcda5
6
+ metadata.gz: e109c329657a2318947e0225c81f7ffcf2eced73cda705fa2dff5996c7e5f65fead30dad4c8e0023bb0d5ef71d31edce2f70a36aab3b638d556808ed66c7e928
7
+ data.tar.gz: 6db5efc1e016e5de43645123db4a3f4b2e47ef538c05fa116ce01c66a539d65bfda0000b1e7c16e6a05ef28bf06a39bcf560b9b6b0bece8f4d6584eddbe01a88
data/README.md CHANGED
@@ -50,26 +50,30 @@ ParallelServer::Prefork の動作を設定するパラメータ。
50
50
  空きスレッド数を指定します(デフォルト: 5)。
51
51
  少なくともこの数だけの接続を受け付けられるようにプロセス数を調整します。
52
52
 
53
- `:back_log` :
54
- 待ち受けポートの listen back log を指定します(デフォルト: standby_threads と同じ)。
53
+ `:listen_backlog` :
54
+ 待ち受けポートの listen backlog を指定します(デフォルト: nil)。
55
+ 未指定時は何も設定しないので backlog の値はシステム依存です。
55
56
 
56
57
  `:max_idle` :
57
58
  指定秒数の間、クライアントからの新たな接続がないとプロセスを終了します(デフォルト: 10)。
58
59
  生成後一度も接続されていないプロセスはこのパラメータの影響を受けません。
59
60
  max_idle 経過後でもクライアントと接続中であればプロセスは終了しません。ただし `:min_processes`, `:max_processes` のカウント対象外です。
60
61
 
61
- `:on_child_start` :
62
+ `:on_start` :
62
63
  子プロセス起動時に*子プロセス側*で実行される処理を Proc で指定します。Proc 実行時の引数はありません。
63
64
 
65
+ `:on_child_start` :
66
+ 子プロセス起動時に*親プロセス側*で実行される処理を Proc で指定します。Proc 実行時の引数はプロセスID(Integer)です。
67
+
64
68
  `:on_child_exit` :
65
69
  子プロセス終了時に*親プロセス側*で実行される処理を Proc で指定します。Proc 実行時の引数はプロセスID(Integer)と終了ステータス(Process::Status)です。
66
70
 
67
71
  ### #start
68
72
 
69
- * `start{|sock, addr| ...}`
73
+ * `start{|sock, addr, child| ...}`
70
74
 
71
75
  待ち受けを開始します。クライアントから接続する毎にスレッドを生成して、ブロックを実行します。
72
- ブロックパラメータは Socket と Addrinfo です。
76
+ ブロックパラメータは Socket と Addrinfo と ParallelServer::Prefork::Child です。
73
77
 
74
78
  ### #reload
75
79
 
@@ -9,6 +9,8 @@ module ParallelServer
9
9
  DEFAULT_STANDBY_THREADS = 5
10
10
  DEFAULT_MAX_IDLE = 10
11
11
 
12
+ attr_reader :child_status
13
+
12
14
  # @!macro [new] args
13
15
  # @param host [String] hostname or IP address
14
16
  # @param port [Integer / String] port number / service name
@@ -17,39 +19,43 @@ module ParallelServer
17
19
  # @option opts [Integer] :max_processes (20) maximum processes
18
20
  # @option opts [Integer] :max_idle (10) cihld process exits if max_idle seconds is expired
19
21
  # @option opts [Integer] :max_threads (1) maximum threads per process
20
- # @option opts [#call] :on_child_start (nil) object#call() is invoked when child process start. This is called in child process.
21
- # @option opts [#call] :on_child_exit (nil) object#call(pid, status) is invoked when child process stop. This is call in parent process.
22
22
  # @option opts [Integer] :standby_threads (5) keep free processes or threads
23
- # @option opts [Integer] :back_log (same as standby_threads) listen back log
23
+ # @option opts [Integer] :listen_backlog (nil) listen backlog
24
+ # @option opts [#call] :on_start (nil) object#call() is invoked when child process start. This is called in child process.
25
+ # @option opts [#call] :on_child_start (nil) object#call(pid) is invoked when child process exit. This is call in parent process.
26
+ # @option opts [#call] :on_child_exit (nil) object#call(pid, status) is invoked when child process exit. This is call in parent process.
24
27
 
25
28
  # @overload initialize(host=nil, port, opts={})
26
29
  # @!macro args
27
30
  def initialize(*args)
28
31
  host, port, opts = parse_args(*args)
29
32
  @host, @port, @opts = host, port, opts
30
- @min_processes = opts[:min_processes] || DEFAULT_MIN_PROCESSES
31
- @max_processes = opts[:max_processes] || DEFAULT_MAX_PROCESSES
32
- @max_threads = opts[:max_threads] || DEFAULT_MAX_THREADS
33
- @on_child_start = opts[:on_child_start]
34
- @on_child_exit = opts[:on_child_exit]
35
- @standby_threads = opts[:standby_threads] || DEFAULT_STANDBY_THREADS
36
- @back_log = opts[:back_log] || @standby_threads
33
+ @min_processes = @opts[:min_processes] || DEFAULT_MIN_PROCESSES
34
+ @max_processes = @opts[:max_processes] || DEFAULT_MAX_PROCESSES
35
+ @max_threads = @opts[:max_threads] || DEFAULT_MAX_THREADS
36
+ @standby_threads = @opts[:standby_threads] || DEFAULT_STANDBY_THREADS
37
+ @listen_backlog = @opts[:listen_backlog]
38
+ @on_start = @opts[:on_start]
39
+ @on_child_start = @opts[:on_child_start]
40
+ @on_child_exit = @opts[:on_child_exit]
37
41
  @from_child = {} # IO => pid
38
42
  @to_child = {} # pid => IO
39
43
  @child_status = {} # pid => Hash
40
44
  @children = [] # pid
45
+ @thread_to_child = {} # pid => Thread
41
46
  end
42
47
 
43
48
  # @return [void]
44
- # @yield [sock, addr]
49
+ # @yield [sock, addr, child]
45
50
  # @yieldparam sock [Socket]
46
51
  # @yieldparam addr [Addrinfo]
52
+ # @yieldparam child [ParallelServer::Prefork::Child]
47
53
  def start(&block)
48
54
  raise 'block required' unless block
49
55
  @block = block
50
56
  @loop = true
51
57
  @sockets = Socket.tcp_server_sockets(@host, @port)
52
- @sockets.each{|s| s.listen(@back_log)}
58
+ @sockets.each{|s| s.listen(@listen_backlog)} if @listen_backlog
53
59
  @reload_args = nil
54
60
  while @loop
55
61
  do_reload if @reload_args
@@ -59,6 +65,8 @@ module ParallelServer
59
65
  @sockets.each(&:close)
60
66
  @to_child.values.each(&:close)
61
67
  @to_child.clear
68
+ @thread_to_child.values.each(&:exit)
69
+ @thread_to_child.clear
62
70
  Thread.new{wait_all_children}
63
71
  end
64
72
 
@@ -77,34 +85,56 @@ module ParallelServer
77
85
  @min_processes = @opts[:min_processes] || DEFAULT_MIN_PROCESSES
78
86
  @max_processes = @opts[:max_processes] || DEFAULT_MAX_PROCESSES
79
87
  @max_threads = @opts[:max_threads] || DEFAULT_MAX_THREADS
88
+ @standby_threads = @opts[:standby_threads] || DEFAULT_STANDBY_THREADS
89
+ @listen_backlog = @opts[:listen_backlog]
90
+ @on_start = @opts[:on_start]
80
91
  @on_child_start = @opts[:on_child_start]
81
92
  @on_child_exit = @opts[:on_child_exit]
82
- @standby_threads = @opts[:standby_threads] || DEFAULT_STANDBY_THREADS
83
- @back_log = @opts[:back_log] || @standby_threads
84
93
 
85
- data = {}
94
+ address_changed = false
86
95
  if @host != host || @port != port
87
96
  @host, @port = host, port
88
97
  @sockets.each(&:close)
89
98
  @sockets = Socket.tcp_server_sockets(@host, @port)
90
- @sockets.each{|s| s.listen(@back_log)}
91
- data[:address_changed] = true
99
+ @sockets.each{|s| s.listen(@listen_backlog)} if @listen_backlog
100
+ address_changed = true
92
101
  end
93
- data[:opts] = @opts.select{|_, value| Marshal.dump(value) rescue nil}
102
+
103
+ reload_children(address_changed)
104
+ end
105
+
106
+ # @param address_changed [true/false]
107
+ # @return [void]
108
+ def reload_children(address_changed=false)
109
+ data = {}
110
+ data[:address_changed] = address_changed
111
+ data[:options] = @opts.select{|_, value| Marshal.dump(value) rescue nil}
94
112
  data = Marshal.dump(data)
95
- @to_child.values.each do |pipe|
96
- talk_to_child pipe, data
113
+ talk_to_children data
114
+ end
115
+
116
+ # @param data [String]
117
+ # @return [void]
118
+ def talk_to_children(data)
119
+ @data_to_child = data
120
+ @thread_to_child.values.each do |thr|
121
+ begin
122
+ thr.run
123
+ rescue ThreadError
124
+ # try to run dead thread. ignore it.
125
+ end
97
126
  end
98
127
  end
99
128
 
100
129
  # @param io [IO]
101
- # @param data [String]
102
130
  # @return [void]
103
- def talk_to_child(io, data)
104
- io.puts data.length
105
- io.write data
106
- rescue Errno::EPIPE
107
- # ignore
131
+ def talk_to_child_loop(io)
132
+ while true
133
+ Thread.stop
134
+ data = @data_to_child
135
+ io.puts data.length
136
+ io.write data
137
+ end
108
138
  end
109
139
 
110
140
  # @return [void]
@@ -145,10 +175,19 @@ module ParallelServer
145
175
  readable.each do |from_child|
146
176
  pid = @from_child[from_child]
147
177
  if st = read_child_status(from_child)
148
- @child_status[pid] = st
178
+ @child_status[pid].update st
179
+ if st[:status] == :stop
180
+ @to_child[pid].close rescue nil
181
+ @to_child.delete pid
182
+ @thread_to_child[pid].exit rescue nil
183
+ @thread_to_child.delete pid
184
+ end
149
185
  else
150
186
  @from_child.delete from_child
187
+ @to_child[pid].close rescue nil
151
188
  @to_child.delete pid
189
+ @thread_to_child[pid].exit rescue nil
190
+ @thread_to_child.delete pid
152
191
  @child_status.delete pid
153
192
  from_child.close
154
193
  end
@@ -187,14 +226,14 @@ module ParallelServer
187
226
  # @return [Array<Integer, Integer>]
188
227
  def current_capacity_and_connections
189
228
  values = @child_status.values
190
- capa = values.map{|st| st[:capacity]}.reduce(&:+).to_i
191
- conn = values.map{|st| st[:running]}.reduce(&:+).to_i
229
+ capa = values.count{|st| st[:status] == :run} * @max_threads
230
+ conn = values.map{|st| st[:connections].count}.reduce(&:+).to_i
192
231
  return [capa, conn]
193
232
  end
194
233
 
195
234
  # @return [Integer]
196
235
  def available_children
197
- @child_status.values.select{|st| st[:capacity] > 0}.size
236
+ @child_status.values.count{|st| st[:status] == :run}
198
237
  end
199
238
 
200
239
  # @return [void]
@@ -222,41 +261,47 @@ module ParallelServer
222
261
  @to_child.values.each(&:close)
223
262
  from_child[0].close
224
263
  to_child[1].close
225
- @on_child_start.call if @on_child_start
264
+ @on_start.call if @on_start
226
265
  Child.new(@sockets, @opts, from_child[1], to_child[0]).start(@block)
227
266
  end
228
267
  from_child[1].close
229
268
  to_child[0].close
230
269
  @from_child[from_child[0]] = pid
231
270
  @to_child[pid] = to_child[1]
271
+ @thread_to_child[pid] = Thread.new(to_child[1]){|io| talk_to_child_loop(io)}
232
272
  @children.push pid
233
- @child_status[pid] = {capacity: @max_threads, running: 0}
273
+ @child_status[pid] = {status: :run, connections: {}}
274
+ @on_child_start.call(pid) if @on_child_start
234
275
  end
235
276
 
236
277
  class Child
278
+
279
+ attr_reader :options
280
+
237
281
  # @param sockets [Array<Socket>]
238
282
  # @param opts [Hash]
239
283
  # @param to_parent [IO]
240
284
  # @param from_parent [IO]
241
285
  def initialize(sockets, opts, to_parent, from_parent)
242
286
  @sockets = sockets
243
- @opts = opts
287
+ @options = opts
244
288
  @to_parent = to_parent
245
289
  @from_parent = from_parent
246
290
  @threads = {}
247
291
  @threads_mutex = Mutex.new
248
292
  @threads_cv = ConditionVariable.new
293
+ @parent_mutex = Mutex.new
249
294
  @status = :run
250
295
  end
251
296
 
252
297
  # @return [Integer]
253
298
  def max_threads
254
- @opts[:max_threads] || DEFAULT_MAX_THREADS
299
+ @options[:max_threads] || DEFAULT_MAX_THREADS
255
300
  end
256
301
 
257
302
  # @return [Integer]
258
303
  def max_idle
259
- @opts[:max_idle] || DEFAULT_MAX_IDLE
304
+ @options[:max_idle] || DEFAULT_MAX_IDLE
260
305
  end
261
306
 
262
307
  # @param block [#call]
@@ -268,12 +313,12 @@ module ParallelServer
268
313
  sock, addr = accept(first)
269
314
  break unless sock
270
315
  first = false
271
- thr = Thread.new(sock, addr){|s, a| run(s, a, block)}
272
- connected(thr)
316
+ Thread.new(sock, addr){|s, a| run(s, a, block)}
273
317
  end
318
+ @status = :stop
274
319
  @sockets.each(&:close)
275
320
  @threads_mutex.synchronize do
276
- notice_status
321
+ notify_status
277
322
  end
278
323
  wait_all_connections
279
324
  end
@@ -294,7 +339,7 @@ module ParallelServer
294
339
  raise unless data.size == len
295
340
  data = Marshal.load(data)
296
341
  raise if data[:address_changed]
297
- @opts.update data[:opts]
342
+ @options.update data[:options]
298
343
  rescue
299
344
  @status = :stop
300
345
  end
@@ -308,12 +353,12 @@ module ParallelServer
308
353
  end
309
354
  end
310
355
 
311
- # @param thread [Thread]
356
+ # @param addr [Addrinfo]
312
357
  # @return [void]
313
- def connected(thread)
358
+ def connected(addr)
314
359
  @threads_mutex.synchronize do
315
- @threads[thread] = true
316
- notice_status
360
+ @threads[Thread.current] = addr
361
+ notify_status
317
362
  end
318
363
  end
319
364
 
@@ -321,16 +366,17 @@ module ParallelServer
321
366
  def disconnect
322
367
  @threads_mutex.synchronize do
323
368
  @threads.delete Thread.current
324
- notice_status
369
+ notify_status
325
370
  @threads_cv.signal
326
371
  end
327
372
  end
328
373
 
329
374
  # @return [void]
330
- def notice_status
375
+ def notify_status
376
+ connections = Hash[@threads.map{|thr, adr| [thr.object_id, adr]}]
331
377
  status = {
332
- running: @threads.size,
333
- capacity: @status == :run ? max_threads : 0,
378
+ status: @status,
379
+ connections: connections,
334
380
  }
335
381
  data = Marshal.dump(status)
336
382
  @to_parent.puts data.length
@@ -344,7 +390,8 @@ module ParallelServer
344
390
  # @param block [#call]
345
391
  # @return [void]
346
392
  def run(sock, addr, block)
347
- block.call(sock, addr)
393
+ connected(addr)
394
+ block.call(sock, addr, self)
348
395
  rescue Exception => e
349
396
  STDERR.puts e.inspect, e.backtrace.inspect
350
397
  ensure
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel_server
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomita Masahiro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-30 00:00:00.000000000 Z
11
+ date: 2014-08-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parallel TCP Server library. This is easy to make Multi-Process / Multi-Thread
14
14
  server