parallel_server 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45b4976310bb10eafcde6ffd88c58109b37e8484
4
- data.tar.gz: f82ca0d3995fd5ea389696d3cbf8f4db793ca6cc
3
+ metadata.gz: c5d5502b45b7905a8987e8ca85d33718086a57bc
4
+ data.tar.gz: 9cba1d1da00b7b52d335b615ff0c54d52a56eb45
5
5
  SHA512:
6
- metadata.gz: e109c329657a2318947e0225c81f7ffcf2eced73cda705fa2dff5996c7e5f65fead30dad4c8e0023bb0d5ef71d31edce2f70a36aab3b638d556808ed66c7e928
7
- data.tar.gz: 6db5efc1e016e5de43645123db4a3f4b2e47ef538c05fa116ce01c66a539d65bfda0000b1e7c16e6a05ef28bf06a39bcf560b9b6b0bece8f4d6584eddbe01a88
6
+ metadata.gz: 01383431223dc5812e135947fbb7d98ad5cfefa88009c408e9e51d20a82bb8a7db48cfc81e458918ee65be98b8fa921f65291ef9848d82ec7165d7c553961807
7
+ data.tar.gz: 42202bdc861f0cc9e95b0acf9d01e38ea81540385d1a39bee34ec657333dd3bada3dcd82f7582d26eff1d4c518a7c6dcb07b3c05146bd29464a11aaad25e7d64
data/README.md CHANGED
@@ -59,9 +59,16 @@ ParallelServer::Prefork の動作を設定するパラメータ。
59
59
  生成後一度も接続されていないプロセスはこのパラメータの影響を受けません。
60
60
  max_idle 経過後でもクライアントと接続中であればプロセスは終了しません。ただし `:min_processes`, `:max_processes` のカウント対象外です。
61
61
 
62
+ `:max_use` :
63
+ クライアントがこの回数の接続を受け付けるとプロセスを終了します(デフォルト: 1000)。
64
+ max_use 経過後でもクライアントと接続中であればプロセスは終了しません。ただし `:min_processes`, `:max_processes` のカウント対象外です。
65
+
62
66
  `:on_start` :
63
67
  子プロセス起動時に*子プロセス側*で実行される処理を Proc で指定します。Proc 実行時の引数はありません。
64
68
 
69
+ `:on_reload` :
70
+ reload 時に*子プロセス側*で実行される処理を Proc で指定します。Proc 実行時の引数は親プロセスから渡されたオプション(Hash)です。
71
+
65
72
  `:on_child_start` :
66
73
  子プロセス起動時に*親プロセス側*で実行される処理を Proc で指定します。Proc 実行時の引数はプロセスID(Integer)です。
67
74
 
@@ -8,6 +8,7 @@ module ParallelServer
8
8
  DEFAULT_MAX_THREADS = 1
9
9
  DEFAULT_STANDBY_THREADS = 5
10
10
  DEFAULT_MAX_IDLE = 10
11
+ DEFAULT_MAX_USE = 1000
11
12
 
12
13
  attr_reader :child_status
13
14
 
@@ -18,10 +19,12 @@ module ParallelServer
18
19
  # @option opts [Integer] :min_processes (5) minimum processes
19
20
  # @option opts [Integer] :max_processes (20) maximum processes
20
21
  # @option opts [Integer] :max_idle (10) cihld process exits if max_idle seconds is expired
22
+ # @option opts [Integer] :max_use (1000) child process exits if it is connected max_use times.
21
23
  # @option opts [Integer] :max_threads (1) maximum threads per process
22
24
  # @option opts [Integer] :standby_threads (5) keep free processes or threads
23
25
  # @option opts [Integer] :listen_backlog (nil) listen backlog
24
26
  # @option opts [#call] :on_start (nil) object#call() is invoked when child process start. This is called in child process.
27
+ # @option opts [#call] :on_reload (nil) object#call(hash) is invoked when reload. This is called in child process.
25
28
  # @option opts [#call] :on_child_start (nil) object#call(pid) is invoked when child process exit. This is call in parent process.
26
29
  # @option opts [#call] :on_child_exit (nil) object#call(pid, status) is invoked when child process exit. This is call in parent process.
27
30
 
@@ -30,19 +33,13 @@ module ParallelServer
30
33
  def initialize(*args)
31
34
  host, port, opts = parse_args(*args)
32
35
  @host, @port, @opts = host, port, opts
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]
36
+ set_variables_from_opts
41
37
  @from_child = {} # IO => pid
42
38
  @to_child = {} # pid => IO
43
39
  @child_status = {} # pid => Hash
44
40
  @children = [] # pid
45
41
  @thread_to_child = {} # pid => Thread
42
+ @loop = true
46
43
  end
47
44
 
48
45
  # @return [void]
@@ -53,7 +50,6 @@ module ParallelServer
53
50
  def start(&block)
54
51
  raise 'block required' unless block
55
52
  @block = block
56
- @loop = true
57
53
  @sockets = Socket.tcp_server_sockets(@host, @port)
58
54
  @sockets.each{|s| s.listen(@listen_backlog)} if @listen_backlog
59
55
  @reload_args = nil
@@ -62,12 +58,13 @@ module ParallelServer
62
58
  watch_children
63
59
  adjust_children
64
60
  end
65
- @sockets.each(&:close)
66
- @to_child.values.each(&:close)
61
+ ensure
62
+ @sockets.each{|s| s.close rescue nil} if @sockets
63
+ @to_child.values.each{|s| s.close rescue nil}
67
64
  @to_child.clear
68
65
  @thread_to_child.values.each(&:exit)
69
66
  @thread_to_child.clear
70
- Thread.new{wait_all_children}
67
+ Timeout.timeout(1){wait_all_children} rescue Thread.new{wait_all_children}
71
68
  end
72
69
 
73
70
  # @overload reload(host=nil, port, opts={})
@@ -77,19 +74,25 @@ module ParallelServer
77
74
  @reload_args = parse_args(*args)
78
75
  end
79
76
 
77
+ # @return [void]
78
+ def stop
79
+ @loop = false
80
+ end
81
+
82
+ # @return [void]
83
+ def stop!
84
+ Process.kill 'TERM', *@children rescue nil
85
+ @loop = false
86
+ end
87
+
88
+ private
89
+
80
90
  # @return [void]
81
91
  def do_reload
82
92
  host, port, @opts = @reload_args
83
93
  @reload_args = nil
84
-
85
- @min_processes = @opts[:min_processes] || DEFAULT_MIN_PROCESSES
86
- @max_processes = @opts[:max_processes] || DEFAULT_MAX_PROCESSES
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]
91
- @on_child_start = @opts[:on_child_start]
92
- @on_child_exit = @opts[:on_child_exit]
94
+ old_listen_backlog = @listen_backlog
95
+ set_variables_from_opts
93
96
 
94
97
  address_changed = false
95
98
  if @host != host || @port != port
@@ -98,6 +101,8 @@ module ParallelServer
98
101
  @sockets = Socket.tcp_server_sockets(@host, @port)
99
102
  @sockets.each{|s| s.listen(@listen_backlog)} if @listen_backlog
100
103
  address_changed = true
104
+ elsif @listen_backlog != old_listen_backlog
105
+ @sockets.each{|s| s.listen(@listen_backlog)} if @listen_backlog
101
106
  end
102
107
 
103
108
  reload_children(address_changed)
@@ -109,14 +114,13 @@ module ParallelServer
109
114
  data = {}
110
115
  data[:address_changed] = address_changed
111
116
  data[:options] = @opts.select{|_, value| Marshal.dump(value) rescue nil}
112
- data = Marshal.dump(data)
113
117
  talk_to_children data
114
118
  end
115
119
 
116
120
  # @param data [String]
117
121
  # @return [void]
118
122
  def talk_to_children(data)
119
- @data_to_child = data
123
+ @data_to_child = Marshal.dump(data)
120
124
  @thread_to_child.values.each do |thr|
121
125
  begin
122
126
  thr.run
@@ -129,25 +133,14 @@ module ParallelServer
129
133
  # @param io [IO]
130
134
  # @return [void]
131
135
  def talk_to_child_loop(io)
136
+ data = nil
132
137
  while true
133
- Thread.stop
138
+ Thread.stop if data.nil? || data == @data_to_child
134
139
  data = @data_to_child
135
- io.puts data.length
136
- io.write data
140
+ Conversation._send(io, data)
137
141
  end
138
142
  end
139
143
 
140
- # @return [void]
141
- def stop
142
- @loop = false
143
- end
144
-
145
- # @return [void]
146
- def stop!
147
- Process.kill 'TERM', *@children rescue nil
148
- @loop = false
149
- end
150
-
151
144
  # @overload parse_args(host=nil, port, opts={})
152
145
  # @macro args
153
146
  # @return [Array<String, String, Hash>] hostname, port, option
@@ -174,7 +167,7 @@ module ParallelServer
174
167
  if readable
175
168
  readable.each do |from_child|
176
169
  pid = @from_child[from_child]
177
- if st = read_child_status(from_child)
170
+ if st = Conversation.recv(from_child)
178
171
  @child_status[pid].update st
179
172
  if st[:status] == :stop
180
173
  @to_child[pid].close rescue nil
@@ -198,17 +191,6 @@ module ParallelServer
198
191
  end
199
192
  end
200
193
 
201
- # @param io [IO]
202
- # @return [Hash]
203
- def read_child_status(io)
204
- len = io.gets
205
- return unless len && len =~ /\A\d+\n/
206
- len = len.to_i
207
- data = io.read(len)
208
- return unless data.size == len
209
- Marshal.load(data)
210
- end
211
-
212
194
  # @return [void]
213
195
  def adjust_children
214
196
  (@min_processes - available_children).times do
@@ -274,6 +256,17 @@ module ParallelServer
274
256
  @on_child_start.call(pid) if @on_child_start
275
257
  end
276
258
 
259
+ def set_variables_from_opts
260
+ @min_processes = @opts[:min_processes] || DEFAULT_MIN_PROCESSES
261
+ @max_processes = @opts[:max_processes] || DEFAULT_MAX_PROCESSES
262
+ @max_threads = @opts[:max_threads] || DEFAULT_MAX_THREADS
263
+ @standby_threads = @opts[:standby_threads] || DEFAULT_STANDBY_THREADS
264
+ @listen_backlog = @opts[:listen_backlog]
265
+ @on_start = @opts[:on_start]
266
+ @on_child_start = @opts[:on_child_start]
267
+ @on_child_exit = @opts[:on_child_exit]
268
+ end
269
+
277
270
  class Child
278
271
 
279
272
  attr_reader :options
@@ -304,23 +297,52 @@ module ParallelServer
304
297
  @options[:max_idle] || DEFAULT_MAX_IDLE
305
298
  end
306
299
 
300
+ # @return [Integer]
301
+ def max_use
302
+ @options[:max_use] || DEFAULT_MAX_USE
303
+ end
304
+
307
305
  # @param block [#call]
308
306
  # @return [void]
309
307
  def start(block)
310
- first = true
311
- while @status == :run
312
- wait_thread
313
- sock, addr = accept(first)
314
- break unless sock
315
- first = false
316
- Thread.new(sock, addr){|s, a| run(s, a, block)}
317
- end
318
- @status = :stop
308
+ main_thread = Thread.current
309
+ accept_thread = Thread.new{ accept_loop(block, main_thread) }
310
+ reload_thread = Thread.new{ reload_loop(main_thread) }
311
+
312
+ # wait that accept_loop or reload_loop end
313
+ Thread.stop while @status == :run
314
+
315
+ accept_thread.exit
319
316
  @sockets.each(&:close)
320
317
  @threads_mutex.synchronize do
321
318
  notify_status
322
319
  end
323
320
  wait_all_connections
321
+ reload_thread.exit
322
+ end
323
+
324
+ private
325
+
326
+ # @param block [#call]
327
+ # @param main_thread [Thread]
328
+ # @return [void]
329
+ def accept_loop(block, main_thread)
330
+ count = 0
331
+ while @status == :run
332
+ wait_thread
333
+ sock, addr = accept
334
+ next if sock.nil? && count == 0
335
+ break unless sock
336
+ thr = Thread.new(sock, addr){|s, a| run(s, a, block)}
337
+ @threads_mutex.synchronize do
338
+ @threads[thr] = addr
339
+ end
340
+ count += 1
341
+ break if count >= max_use
342
+ end
343
+ ensure
344
+ @status = :stop
345
+ main_thread.run
324
346
  end
325
347
 
326
348
  # @return [void]
@@ -328,20 +350,25 @@ module ParallelServer
328
350
  @threads.keys.each do |thr|
329
351
  thr.join rescue nil
330
352
  end
353
+ @status = :exit
331
354
  end
332
355
 
356
+ # @param main_thread [Thread]
333
357
  # @return [void]
334
- def reload
335
- len = @from_parent.gets
336
- raise unless len && len =~ /\A\d+\n/
337
- len = len.to_i
338
- data = @from_parent.read(len)
339
- raise unless data.size == len
340
- data = Marshal.load(data)
341
- raise if data[:address_changed]
342
- @options.update data[:options]
343
- rescue
358
+ def reload_loop(main_thread)
359
+ while true
360
+ data = Conversation.recv(@from_parent)
361
+ break unless data
362
+ @options.update data[:options]
363
+ @options[:on_reload].call @options if @options[:on_reload]
364
+ @status = :stop if data[:address_changed]
365
+ @threads_cv.signal
366
+ end
367
+ @from_parent.close
368
+ @from_parent = nil
369
+ ensure
344
370
  @status = :stop
371
+ main_thread.run
345
372
  end
346
373
 
347
374
  # @return [void]
@@ -378,9 +405,7 @@ module ParallelServer
378
405
  status: @status,
379
406
  connections: connections,
380
407
  }
381
- data = Marshal.dump(status)
382
- @to_parent.puts data.length
383
- @to_parent.write data
408
+ Conversation.send(@to_parent, status)
384
409
  rescue Errno::EPIPE
385
410
  # ignore
386
411
  end
@@ -399,20 +424,13 @@ module ParallelServer
399
424
  disconnect
400
425
  end
401
426
 
402
- # @param first [Boolean]
403
427
  # @return [Array<Socket, AddrInfo>]
404
428
  # @return [nil]
405
- def accept(first=nil)
429
+ def accept
406
430
  while true
407
- timer = first ? nil : max_idle
408
- readable, = IO.select(@sockets+[@from_parent], nil, nil, timer)
431
+ readable, = IO.select(@sockets, nil, nil, max_idle)
409
432
  return nil unless readable
410
433
  r, = readable
411
- if r == @from_parent
412
- reload
413
- next if @status == :run
414
- return nil
415
- end
416
434
  begin
417
435
  sock, addr = r.accept_nonblock
418
436
  return [sock, addr]
@@ -422,5 +440,33 @@ module ParallelServer
422
440
  end
423
441
  end
424
442
  end
443
+
444
+ class Conversation
445
+ # @param io [IO]
446
+ # @param msg [Object]
447
+ # @return [void]
448
+ def self.send(io, msg)
449
+ _send(io, Marshal.dump(msg))
450
+ end
451
+
452
+ # @param io [IO]
453
+ # @param data [String] marshaled data
454
+ # @return [void]
455
+ def self._send(io, data)
456
+ io.puts data.length
457
+ io.write data
458
+ end
459
+
460
+ # @param io [IO]
461
+ # @return [Object]
462
+ def self.recv(io)
463
+ len = io.gets
464
+ return unless len && len =~ /\A\d+\n/
465
+ len = len.to_i
466
+ data = io.read(len)
467
+ return unless data && data.size == len
468
+ Marshal.load(data)
469
+ end
470
+ end
425
471
  end
426
472
  end
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.1
4
+ version: 0.1.2
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-08-19 00:00:00.000000000 Z
11
+ date: 2014-10-06 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