fluentd 0.10.6 → 0.10.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

@@ -41,8 +41,9 @@ class FileBufferChunk < BufferChunk
41
41
  end
42
42
 
43
43
  def close
44
+ stat = @file.stat
44
45
  @file.close
45
- if @file.stat.size == 0
46
+ if stat.size == 0
46
47
  File.unlink(@path)
47
48
  end
48
49
  end
@@ -50,10 +50,12 @@ class ForwardInput < Input
50
50
  end
51
51
 
52
52
  def shutdown
53
- @lsock.close
53
+ @loop.watchers.each {|w| w.detach }
54
54
  @loop.stop
55
- @thread.join
56
55
  @usock.close
56
+ TCPSocket.open('127.0.0.1', @port) {|sock| } # FIXME @thread.join blocks without this line
57
+ @thread.join
58
+ @lsock.close
57
59
  end
58
60
 
59
61
  def listen
@@ -21,6 +21,8 @@ module Fluent
21
21
  class HttpInput < Input
22
22
  Plugin.register_input('http', self)
23
23
 
24
+ include DetachMultiProcessMixin
25
+
24
26
  require 'http/parser'
25
27
 
26
28
  def initialize
@@ -68,23 +70,28 @@ class HttpInput < Input
68
70
  end
69
71
  end
70
72
 
71
- # TODO multithreading
72
73
  def start
73
74
  $log.debug "listening http on #{@bind}:#{@port}"
75
+ lsock = TCPServer.new(@bind, @port)
74
76
 
75
- @km = KeepaliveManager.new(@keepalive_timeout)
76
- @lsock = Coolio::TCPServer.new(@bind, @port, Handler, @km, method(:on_request), @body_size_limit)
77
+ detach_multi_process do
78
+ super
79
+ @km = KeepaliveManager.new(@keepalive_timeout)
80
+ #@lsock = Coolio::TCPServer.new(@bind, @port, Handler, @km, method(:on_request), @body_size_limit)
81
+ @lsock = Coolio::TCPServer.new(lsock, nil, Handler, @km, method(:on_request), @body_size_limit)
77
82
 
78
- @loop = Coolio::Loop.new
79
- @loop.attach(@km)
80
- @loop.attach(@lsock)
83
+ @loop = Coolio::Loop.new
84
+ @loop.attach(@km)
85
+ @loop.attach(@lsock)
81
86
 
82
- @thread = Thread.new(&method(:run))
87
+ @thread = Thread.new(&method(:run))
88
+ end
83
89
  end
84
90
 
85
91
  def shutdown
86
- @lsock.close
92
+ @loop.watchers.each {|w| w.detach }
87
93
  @loop.stop
94
+ @lsock.close
88
95
  @thread.join
89
96
  end
90
97
 
@@ -35,8 +35,9 @@ class StreamInput < Input
35
35
  end
36
36
 
37
37
  def shutdown
38
- @lsock.close
38
+ @loop.watchers.each {|w| w.detach }
39
39
  @loop.stop
40
+ @lsock.close
40
41
  @thread.join
41
42
  end
42
43
 
@@ -28,6 +28,7 @@ class TailInput < Input
28
28
 
29
29
  config_param :path, :string
30
30
  config_param :tag, :string
31
+ config_param :pos_file, :string, :default => nil
31
32
 
32
33
  def configure(conf)
33
34
  super
@@ -37,6 +38,12 @@ class TailInput < Input
37
38
  raise ConfigError, "tail: 'path' parameter is required on tail input"
38
39
  end
39
40
 
41
+ if @pos_file
42
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT)
43
+ @pf_file.sync = true
44
+ @pf = PositionFile.parse(@pf_file)
45
+ end
46
+
40
47
  configure_parser(conf)
41
48
  end
42
49
 
@@ -47,16 +54,23 @@ class TailInput < Input
47
54
 
48
55
  def start
49
56
  @loop = Coolio::Loop.new
50
- @paths.each {|path|
57
+ handlers = @paths.map {|path|
51
58
  $log.debug "following tail of #{path}"
52
- @loop.attach Handler.new(path, method(:receive_lines))
59
+ pe = @pf ? @pf[path] : NullPositionEntry.instance
60
+ h = Handler.new(path, pe, method(:receive_lines))
61
+ @loop.attach h
62
+ }
63
+ handlers.each {|h|
64
+ h.on_change # initialize call
53
65
  }
54
66
  @thread = Thread.new(&method(:run))
55
67
  end
56
68
 
57
69
  def shutdown
70
+ @loop.watchers.each {|w| w.detach }
58
71
  @loop.stop
59
72
  @thread.join
73
+ @pf_file.close if @pf_file
60
74
  end
61
75
 
62
76
  def run
@@ -90,11 +104,20 @@ class TailInput < Input
90
104
  return @parser.parse(line)
91
105
  end
92
106
 
93
- # seek to the end of file first.
94
- # logs never duplicate but may be lost if fluent is down.
95
107
  class Handler < Coolio::StatWatcher
96
- def initialize(path, callback)
97
- @pos = File.stat(path).size
108
+ def initialize(path, pe, callback)
109
+ stat = File.lstat(path)
110
+ @pe = pe
111
+ @inode = stat.ino
112
+ if @inode == @pe.read_inode
113
+ # seek to the saved position
114
+ @pos = @pe.read_pos
115
+ else
116
+ # seek to the end of file first.
117
+ # logs never duplicate but may be lost if fluent is down.
118
+ @pos = stat.size
119
+ @pe.update(@inode, stat.size)
120
+ end
98
121
  @buffer = ''
99
122
  @callback = callback
100
123
  super(path)
@@ -102,9 +125,13 @@ class TailInput < Input
102
125
 
103
126
  def on_change
104
127
  lines = []
128
+ inode = nil
105
129
 
106
130
  File.open(path) {|f|
107
- if f.lstat.size < @pos
131
+ stat = f.lstat
132
+ inode = stat.ino
133
+
134
+ if @inode != inode || stat.size < @pos
108
135
  # moved or deleted
109
136
  @pos = 0
110
137
  else
@@ -113,13 +140,13 @@ class TailInput < Input
113
140
 
114
141
  line = f.gets
115
142
  unless line
116
- return
143
+ break
117
144
  end
118
145
 
119
146
  @buffer << line
120
147
  unless line[line.length-1] == ?\n
121
148
  @pos = f.pos
122
- return
149
+ break
123
150
  end
124
151
 
125
152
  lines << @buffer
@@ -136,11 +163,108 @@ class TailInput < Input
136
163
  @pos = f.pos
137
164
  }
138
165
 
166
+ if @inode != inode
167
+ @pe.update(inode, @pos)
168
+ @inode = inode
169
+ else
170
+ @pe.update_pos(@pos)
171
+ end
172
+
139
173
  @callback.call(lines)
140
174
 
141
175
  rescue Errno::ENOENT
142
176
  # moved or deleted
143
177
  @pos = 0
178
+ # TODO rescue
179
+ end
180
+ end
181
+
182
+ # pos inode
183
+ # ffffffffffffffff\tffffffff\n
184
+ class PositionEntry
185
+ POS_SIZE = 16
186
+ INO_OFFSET = 17
187
+ INO_SIZE = 8
188
+ LN_OFFSET = 25
189
+ SIZE = 26
190
+
191
+ def initialize(file, seek)
192
+ @file = file
193
+ @seek = seek
194
+ end
195
+
196
+ def update(ino, pos)
197
+ @file.pos = @seek
198
+ @file.write "%016x\t%08x" % [pos, ino]
199
+ @inode = ino
200
+ end
201
+
202
+ def update_pos(pos)
203
+ @file.pos = @seek
204
+ @file.write "%016x" % pos
205
+ end
206
+
207
+ def read_inode
208
+ @file.pos = @seek + INO_OFFSET
209
+ @file.read(8).to_i(16)
210
+ end
211
+
212
+ def read_pos
213
+ @file.pos = @seek
214
+ @file.read(16).to_i(16)
215
+ end
216
+ end
217
+
218
+ class PositionFile
219
+ def initialize(file, map, last_pos)
220
+ @file = file
221
+ @map = map
222
+ @last_pos = last_pos
223
+ end
224
+
225
+ def [](path)
226
+ if m = @map[path]
227
+ return m
228
+ end
229
+
230
+ @file.pos = @last_pos
231
+ @file.write path
232
+ @file.write "\t"
233
+ seek = @file.pos
234
+ @file.write "0000000000000000\t00000000\n"
235
+ @last_pos = @file.pos
236
+
237
+ @map[path] = PositionEntry.new(@file, seek)
238
+ end
239
+
240
+ def self.parse(file)
241
+ map = {}
242
+ file.pos = 0
243
+ file.each_line {|line|
244
+ m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
245
+ next unless m
246
+ path = m[1]
247
+ pos = m[2].to_i(16)
248
+ ino = m[3].to_i(16)
249
+ seek = file.pos - line.bytesize + path.bytesize + 1
250
+ map[path] = PositionEntry.new(file, seek)
251
+ }
252
+ new(file, map, file.pos)
253
+ end
254
+ end
255
+
256
+ class NullPositionEntry
257
+ require 'singleton'
258
+ include Singleton
259
+ def update(ino, pos)
260
+ end
261
+ def update_pos(pos)
262
+ end
263
+ def read_pos
264
+ 0
265
+ end
266
+ def read_inode
267
+ 0
144
268
  end
145
269
  end
146
270
  end
@@ -18,7 +18,7 @@
18
18
  module Fluent
19
19
 
20
20
 
21
- class ForwardOutput < BufferedOutput
21
+ class ForwardOutput < ObjectBufferedOutput
22
22
  Plugin.register_output('forward', self)
23
23
 
24
24
  def initialize
@@ -108,9 +108,10 @@ class ForwardOutput < BufferedOutput
108
108
 
109
109
  def shutdown
110
110
  @finished = true
111
- @usock.close
111
+ @loop.watchers.each {|w| w.detach }
112
112
  @loop.stop
113
113
  @thread.join
114
+ @usock.close
114
115
  end
115
116
 
116
117
  def run
@@ -128,14 +129,14 @@ class ForwardOutput < BufferedOutput
128
129
  end
129
130
  end
130
131
 
131
- def write(chunk)
132
+ def write_objects(tag, es)
132
133
  wlen = @weight_array.length
133
134
  wlen.times do
134
135
  node = @weight_array[@rr]
135
136
  @rr = (@rr + 1) % wlen
136
137
 
137
138
  if node.available?
138
- send_data(node, chunk)
139
+ send_data(node, tag, es)
139
140
  return
140
141
  end
141
142
  end
@@ -147,7 +148,7 @@ class ForwardOutput < BufferedOutput
147
148
  # MessagePack FixArray length = 2
148
149
  FORWARD_HEADER = [0x92].pack('C')
149
150
 
150
- def send_data(node, chunk)
151
+ def send_data(node, tag, es)
151
152
  sock = connect(node)
152
153
  begin
153
154
  opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
@@ -160,10 +161,10 @@ class ForwardOutput < BufferedOutput
160
161
  sock.write FORWARD_HEADER
161
162
 
162
163
  # writeRaw(tag)
163
- sock.write chunk.key.to_msgpack # tag
164
+ sock.write tag.to_msgpack # tag
164
165
 
165
166
  # beginRaw(size)
166
- sz = chunk.size
167
+ sz = es.size
167
168
  #if sz < 32
168
169
  # # FixRaw
169
170
  # sock.write [0xa0 | sz].pack('C')
@@ -176,7 +177,7 @@ class ForwardOutput < BufferedOutput
176
177
  #end
177
178
 
178
179
  # writeRawBody(packed_es)
179
- chunk.write_to(sock)
180
+ es.write_to(sock)
180
181
  ensure
181
182
  sock.close
182
183
  end
@@ -202,7 +203,12 @@ class ForwardOutput < BufferedOutput
202
203
  return if @finished
203
204
  @nodes.each_pair {|sockaddr,n|
204
205
  n.tick
205
- @usock.send "", 0, sockaddr
206
+ begin
207
+ @usock.send "", 0, sockaddr
208
+ rescue
209
+ # TODO log
210
+ $log.debug "failed to send heartbeat packet to #{Socket.unpack_sockaddr_in(sockaddr).reverse.join(':')}", :error=>$!
211
+ end
206
212
  }
207
213
  end
208
214
 
@@ -0,0 +1,469 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+
21
+ class ProcessDetachInterrupt < StandardError
22
+ end
23
+
24
+
25
+ class DetachProcessManager
26
+ require 'singleton'
27
+ include Singleton
28
+
29
+ class Broker
30
+ def initialize
31
+ end
32
+
33
+ def engine
34
+ Engine
35
+ end
36
+ end
37
+
38
+ def initialize
39
+ require 'drb'
40
+ DRb.start_service("drbunix:", Broker.new)
41
+ @parent_uri = DRb.uri
42
+ end
43
+
44
+ def fork(delegate_object)
45
+ ipr, ipw = IO.pipe
46
+ opr, opw = IO.pipe
47
+
48
+ pid = Process.fork
49
+ if pid
50
+ # parent process
51
+ ipw.close
52
+ opr.close
53
+ forward_thread = process_parent(ipr, opw, pid, delegate_object)
54
+ return pid, forward_thread
55
+ end
56
+
57
+ # child process
58
+ ipr.close
59
+ opw.close
60
+ forward_thread = process_child(ipw, opr, delegate_object)
61
+ return nil, forward_thread
62
+ end
63
+
64
+ def setup_delegate
65
+ end
66
+
67
+ private
68
+ def read_header(ipr)
69
+ sz = ipr.read(4).unpack('N')[0]
70
+ ipr.read(sz)
71
+ end
72
+
73
+ def send_header(ipw, data)
74
+ ipw.write [data.bytesize].pack('N')
75
+ ipw.write data
76
+ ipw.flush
77
+ end
78
+
79
+ private
80
+ def process_child(ipw, opr, delegate_object)
81
+ fwd = new_forwarder(ipw, 0.5) # TODO interval
82
+ Engine.define_singleton_method(:emit_stream) do |tag,es|
83
+ fwd.emit(tag, es)
84
+ end
85
+
86
+ DRb.start_service("drbunix:", delegate_object)
87
+ child_uri = DRb.uri
88
+
89
+ send_header(ipw, child_uri)
90
+
91
+ forward_thread = Thread.new(opr, delegate_object, &method(:output_forward_main))
92
+
93
+ override_shared_methods(@parent_uri)
94
+
95
+ return forward_thread
96
+ end
97
+
98
+ def override_shared_methods(parent_uri)
99
+ broker = DRbObject.new_with_uri(parent_uri)
100
+ shared_methods.each {|(broker_accessor,target,name)|
101
+ remote = broker.send(broker_accessor)
102
+ target.define_singleton_method(name) do |*args,&block|
103
+ remote.send(name, *args, &block)
104
+ end
105
+ }
106
+ end
107
+
108
+ def shared_methods
109
+ [
110
+ #[:engine, Engine, :flush!],
111
+ #[:engine, Engine, :stop],
112
+ ]
113
+ end
114
+
115
+ def process_parent(ipr, opw, pid, delegate_object)
116
+ child_uri = read_header(ipr)
117
+
118
+ forward_thread = Thread.new(ipr, pid, &method(:input_forward_main))
119
+
120
+ #override_delegate_methods(delegate_object, child_uri)
121
+
122
+ fwd = new_forwarder(opw, 0.5) # TODO interval
123
+ #delegate_object.define_singleton_method(:emit) do |tag,es,chain|
124
+ # chain.next
125
+ # fwd.emit(tag, es)
126
+ #end
127
+
128
+ forward_thread.define_singleton_method(:forwarder) do
129
+ fwd
130
+ end
131
+
132
+ return forward_thread
133
+ end
134
+
135
+ #def override_delegate_methods(target, child_uri)
136
+ # remote = DRbObject.new_with_uri(child_uri)
137
+ # delegate_methods(target).each {|name|
138
+ # target.define_singleton_method(name) do |*args,&block|
139
+ # remote.send(name, *args, &block)
140
+ # end
141
+ # }
142
+ #end
143
+ #
144
+ #def delegate_methods(target)
145
+ # target.methods - Object.public_instance_methods
146
+ #end
147
+
148
+ def output_forward_main(opr, target)
149
+ read_event_stream(opr) {|tag,es|
150
+ # FIXME error
151
+ begin
152
+ target.emit(tag, es, NullOutputChain.instance)
153
+ rescue
154
+ $log.warn "failed to emit", :error=>$!.to_s, :pid=>Process.pid
155
+ $log.warn_backtrace
156
+ end
157
+ }
158
+ rescue
159
+ $log.error "error on output process forwarding thread", :error=>$!.to_s, :pid=>Process.pid
160
+ $log.error_backtrace
161
+ raise
162
+ end
163
+
164
+ def input_forward_main(ipr, pid)
165
+ read_event_stream(ipr) {|tag,es|
166
+ Engine.emit_stream(tag, es)
167
+ }
168
+ rescue
169
+ $log.error "error on input process forwarding thread", :error=>$!.to_s, :pid=>Process.pid
170
+ $log.error_backtrace
171
+ raise
172
+ end
173
+
174
+ def read_event_stream(r, &block)
175
+ u = MessagePack::Unpacker.new(r)
176
+ cached_unpacker = MessagePack::Unpacker.new
177
+ begin
178
+ #buf = ''
179
+ #map = {}
180
+ #while true
181
+ # r.readpartial(64*1024, buf)
182
+ # u.feed_each(buf) {|tag,ms|
183
+ # if msbuf = map[tag]
184
+ # msbuf << ms
185
+ # else
186
+ # map[tag] = ms
187
+ # end
188
+ # }
189
+ # unless map.empty?
190
+ # map.each_pair {|tag,ms|
191
+ # es = MessagePackEventStream.new(ms, cached_unpacker)
192
+ # block.call(tag, es)
193
+ # }
194
+ # map.clear
195
+ # end
196
+ #end
197
+ u.each {|tag,ms|
198
+ es = MessagePackEventStream.new(ms, cached_unpacker)
199
+ block.call(tag, es)
200
+ }
201
+ rescue EOFError
202
+ ensure
203
+ r.close
204
+ end
205
+ end
206
+
207
+ def new_forwarder(w, interval)
208
+ if interval < 0.2 # TODO interval
209
+ Forwarder.new(w)
210
+ else
211
+ DelayedForwarder.new(w, interval)
212
+ end
213
+ end
214
+
215
+ class Forwarder
216
+ def initialize(w)
217
+ @w = w
218
+ end
219
+
220
+ def emit(tag, es)
221
+ ms = es.to_msgpack_stream
222
+ #[tag, ms].to_msgpack(@w) # not thread safe
223
+ @w.write [tag, ms].to_msgpack
224
+ end
225
+ end
226
+
227
+ class DelayedForwarder
228
+ def initialize(w, interval)
229
+ @w = w
230
+ @interval = interval
231
+ @buffer = {}
232
+ Thread.new(&method(:run))
233
+ end
234
+
235
+ def emit(tag, es)
236
+ if ms = @buffer[tag]
237
+ ms << es.to_msgpack_stream
238
+ else
239
+ @buffer[tag] = es.to_msgpack_stream
240
+ end
241
+ end
242
+
243
+ def run
244
+ while true
245
+ sleep @interval
246
+ @buffer.keys.each {|tag|
247
+ if ms = @buffer.delete(tag)
248
+ [tag, ms].to_msgpack(@w)
249
+ #@w.write [tag, ms].to_msgpack
250
+ end
251
+ }
252
+ end
253
+ rescue
254
+ $log.error "error on forwerder thread", :error=>$!.to_s
255
+ $log.error_backtrace
256
+ raise
257
+ end
258
+ end
259
+
260
+ class MultiForwarder
261
+ def initialize(forwarders)
262
+ @forwarders = forwarders
263
+ @rr = 1
264
+ end
265
+
266
+ def emit(tag, es)
267
+ forwarder = @forwarders[@rr]
268
+ @rr = (@rr + 1) % @forwarders.length
269
+ forwarder.emit(tag, es)
270
+ end
271
+ end
272
+ end
273
+
274
+
275
+ module DetachProcessImpl
276
+ def on_detach_process(i)
277
+ end
278
+
279
+ protected
280
+ def detach_process_impl(num, &block)
281
+ children = []
282
+
283
+ num.times do |i|
284
+ pid, forward_thread = DetachProcessManager.instance.fork(self)
285
+
286
+ if pid
287
+ # parent process
288
+ $log.info "detached process", :class=>self.class, :pid=>pid
289
+ children << [pid, forward_thread]
290
+ next
291
+ end
292
+
293
+ # child process
294
+ begin
295
+ on_detach_process(i)
296
+
297
+ block.call
298
+
299
+ # disable Engine.stop called by signal handler
300
+ Engine.define_singleton_method(:stop) do
301
+ # do nothing
302
+ end
303
+
304
+ # override signal handlers called by parent process
305
+ fin = FinishWait.new
306
+ trap :INT do
307
+ fin.stop
308
+ end
309
+ trap :TERM do
310
+ fin.stop
311
+ end
312
+ #forward_thread.join
313
+ fin.wait
314
+
315
+ exit! 0
316
+ ensure
317
+ $log.error "unknown error while shutting down this child process", :error=>$!.to_s, :pid=>Process.pid
318
+ $log.error_backtrace
319
+ end
320
+
321
+ exit! 1
322
+ end
323
+
324
+ # parent process
325
+ # override shutdown method
326
+ define_singleton_method(:shutdown) do
327
+ begin
328
+ children.each {|pair|
329
+ pid = pair[0]
330
+ forward_thread = pair[1]
331
+ if pid
332
+ Process.kill(:TERM, pid)
333
+ forward_thread.join
334
+ Process.waitpid(pid)
335
+ pair[0] = nil
336
+ end
337
+ }
338
+ rescue
339
+ $log.error "unknown error while shutting down remote child process", :error=>$!.to_s
340
+ $log.error_backtrace
341
+ end
342
+ end
343
+
344
+ # override emit method
345
+ forwarders = children.map {|pair| pair[1].forwarder }
346
+ if forwarders.length > 1
347
+ fwd = DetachProcessManager::MultiForwarder.new(forwarders)
348
+ else
349
+ fwd = forwarders[0]
350
+ end
351
+ define_singleton_method(:emit) do |tag,es,chain|
352
+ chain.next
353
+ fwd.emit(tag, es)
354
+ end
355
+ end
356
+
357
+ class FinishWait
358
+ def initialize
359
+ @finished = false
360
+ @mutex = Mutex.new
361
+ @cond = ConditionVariable.new
362
+ end
363
+
364
+ def wait
365
+ @mutex.synchronize do
366
+ until @finished
367
+ @cond.wait(@mutex, 1.0)
368
+ end
369
+ end
370
+ end
371
+
372
+ def stop
373
+ return if @finished
374
+ @finished = true
375
+ @mutex.synchronize do
376
+ @cond.broadcast
377
+ end
378
+ end
379
+
380
+ def finished?
381
+ @finished
382
+ end
383
+ end
384
+ end
385
+
386
+
387
+ module DetachProcessMixin
388
+ include DetachProcessImpl
389
+
390
+ def configure(conf)
391
+ super
392
+
393
+ if detach_process = conf['detach_process']
394
+ b3v = Config.bool_value(detach_process)
395
+ case b3v
396
+ when nil
397
+ num = detach_process.to_i
398
+ if num > 1
399
+ $log.warn "'detach_process' parameter supports only 1 process on this plugin: #{conf}"
400
+ elsif num > 0
401
+ @detach_process = true
402
+ elsif detach_process =~ /0+/
403
+ @detach_process = false
404
+ else
405
+ @detach_process = true
406
+ end
407
+ when true
408
+ @detach_process = true
409
+ when false
410
+ @detach_process = false
411
+ end
412
+ end
413
+ end
414
+
415
+ def detach_process(&block)
416
+ if @detach_process
417
+ detach_process_impl(1, &block)
418
+ else
419
+ block.call
420
+ end
421
+ end
422
+ end
423
+
424
+
425
+ module DetachMultiProcessMixin
426
+ include DetachProcessImpl
427
+
428
+ def initialize
429
+ @detach_process_num = 2
430
+ super
431
+ end
432
+
433
+ def configure(conf)
434
+ super
435
+
436
+ if detach_process = conf['detach_process']
437
+ b3v = Config.bool_value(detach_process)
438
+ case b3v
439
+ when nil
440
+ num = detach_process.to_i
441
+ if num > 0
442
+ @detach_process = true
443
+ @detach_process_num = num
444
+ elsif detach_process =~ /0+/
445
+ @detach_process = false
446
+ else
447
+ @detach_process = true
448
+ end
449
+ when true
450
+ @detach_process = true
451
+ when false
452
+ @detach_process = false
453
+ end
454
+ end
455
+ end
456
+
457
+ protected
458
+ def detach_multi_process(&block)
459
+ if @detach_process
460
+ detach_process_impl(@detach_process_num, &block)
461
+ else
462
+ block.call
463
+ end
464
+ end
465
+ end
466
+
467
+
468
+ end
469
+