fluentd 1.19.3 → 1.20.0
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 +4 -4
- data/AGENTS.md +46 -0
- data/CHANGELOG.md +120 -0
- data/GithubWorkflow.md +1 -1
- data/MAINTAINERS.md +2 -2
- data/README.md +3 -2
- data/Rakefile +2 -1
- data/SECURITY.md +26 -1
- data/lib/fluent/command/cat.rb +3 -3
- data/lib/fluent/compat/exec_util.rb +20 -5
- data/lib/fluent/config/basic_parser.rb +0 -1
- data/lib/fluent/config/literal_parser.rb +22 -10
- data/lib/fluent/config/types.rb +10 -2
- data/lib/fluent/config/yaml_parser/loader.rb +12 -1
- data/lib/fluent/config/yaml_parser/parser.rb +14 -13
- data/lib/fluent/configurable.rb +1 -0
- data/lib/fluent/daemon.rb +2 -1
- data/lib/fluent/env.rb +1 -0
- data/lib/fluent/event.rb +37 -0
- data/lib/fluent/load.rb +0 -1
- data/lib/fluent/log.rb +2 -2
- data/lib/fluent/plugin/base.rb +7 -2
- data/lib/fluent/plugin/buf_file.rb +3 -3
- data/lib/fluent/plugin/buf_file_single.rb +6 -2
- data/lib/fluent/plugin/buffer/chunk.rb +14 -6
- data/lib/fluent/plugin/buffer/file_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer.rb +125 -41
- data/lib/fluent/plugin/compressable.rb +2 -2
- data/lib/fluent/plugin/extractor.rb +17 -6
- data/lib/fluent/plugin/filter_record_transformer.rb +2 -1
- data/lib/fluent/plugin/formatter_out_file.rb +3 -4
- data/lib/fluent/plugin/in_exec.rb +6 -4
- data/lib/fluent/plugin/in_forward.rb +39 -29
- data/lib/fluent/plugin/in_http.rb +83 -2
- data/lib/fluent/plugin/in_monitor_agent.rb +2 -1
- data/lib/fluent/plugin/in_object_space.rb +2 -2
- data/lib/fluent/plugin/in_sample.rb +2 -1
- data/lib/fluent/plugin/in_syslog.rb +72 -6
- data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
- data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
- data/lib/fluent/plugin/in_tail/group_watch.rb +13 -13
- data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
- data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
- data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
- data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
- data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
- data/lib/fluent/plugin/in_tail.rb +324 -815
- data/lib/fluent/plugin/in_unix.rb +5 -3
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_file.rb +10 -4
- data/lib/fluent/plugin/out_forward/connection_manager.rb +18 -0
- data/lib/fluent/plugin/out_forward.rb +21 -2
- data/lib/fluent/plugin/out_http.rb +0 -10
- data/lib/fluent/plugin/output.rb +81 -31
- data/lib/fluent/plugin/parser_csv.rb +7 -2
- data/lib/fluent/plugin/parser_json.rb +25 -12
- data/lib/fluent/plugin/parser_ltsv.rb +2 -4
- data/lib/fluent/plugin/parser_msgpack.rb +7 -6
- data/lib/fluent/plugin/parser_syslog.rb +55 -8
- data/lib/fluent/plugin/parser_tsv.rb +6 -1
- data/lib/fluent/plugin/sd_file.rb +2 -1
- data/lib/fluent/plugin/storage_local.rb +2 -2
- data/lib/fluent/plugin_helper/http_server/server.rb +29 -13
- data/lib/fluent/plugin_helper/http_server.rb +2 -1
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/supervisor.rb +32 -6
- data/lib/fluent/system_config.rb +5 -2
- data/lib/fluent/time.rb +8 -7
- data/lib/fluent/version.rb +1 -1
- data/templates/new_gem/.gitignore +8 -0
- metadata +41 -21
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Fluentd
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
require 'fluent/plugin/input'
|
|
18
|
+
require 'cool.io'
|
|
19
|
+
|
|
20
|
+
module Fluent::Plugin
|
|
21
|
+
class TailInput < Fluent::Plugin::Input
|
|
22
|
+
class StatWatcher < Coolio::StatWatcher
|
|
23
|
+
def initialize(path, log, &callback)
|
|
24
|
+
@callback = callback
|
|
25
|
+
@log = log
|
|
26
|
+
super(path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def on_change(prev, cur)
|
|
30
|
+
@callback.call
|
|
31
|
+
rescue
|
|
32
|
+
@log.error $!.to_s
|
|
33
|
+
@log.error_backtrace
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class TimerTrigger < Coolio::TimerWatcher
|
|
38
|
+
def initialize(interval, log, repeat: true, &callback)
|
|
39
|
+
@log = log
|
|
40
|
+
@callback = callback
|
|
41
|
+
super(interval, repeat)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def on_timer
|
|
45
|
+
@callback.call
|
|
46
|
+
rescue => e
|
|
47
|
+
@log.error e.to_s
|
|
48
|
+
@log.error_backtrace
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Fluentd
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
require 'fluent/plugin/input'
|
|
18
|
+
require 'fluent/file_wrapper'
|
|
19
|
+
require 'fluent/plugin/in_tail/position_file'
|
|
20
|
+
require 'fluent/plugin/in_tail/compatibility'
|
|
21
|
+
|
|
22
|
+
module Fluent::Plugin
|
|
23
|
+
class TailInput < Fluent::Plugin::Input
|
|
24
|
+
class TailWatcher
|
|
25
|
+
def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, file_feed, io_handler_build, metrics)
|
|
26
|
+
@path = target_info.path
|
|
27
|
+
@ino = target_info.ino
|
|
28
|
+
@pe = pe || MemoryPositionEntry.new
|
|
29
|
+
@read_from_head = read_from_head
|
|
30
|
+
@follow_inodes = follow_inodes
|
|
31
|
+
@update_watcher = update_watcher
|
|
32
|
+
@log = log
|
|
33
|
+
@rotate_handler = RotateHandler.new(log, &method(:on_rotate))
|
|
34
|
+
@file_feed = file_feed
|
|
35
|
+
@io_handler = nil
|
|
36
|
+
@io_handler_build = io_handler_build
|
|
37
|
+
@metrics = metrics
|
|
38
|
+
@watchers = []
|
|
39
|
+
@detached = false
|
|
40
|
+
@close_pending = false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
attr_reader :path, :ino
|
|
44
|
+
|
|
45
|
+
def detached?
|
|
46
|
+
@detached
|
|
47
|
+
end
|
|
48
|
+
attr_reader :pe
|
|
49
|
+
attr_reader :file_feed
|
|
50
|
+
attr_accessor :unwatched # This is used for removing position entry from PositionFile
|
|
51
|
+
attr_reader :watchers
|
|
52
|
+
attr_accessor :group_watcher
|
|
53
|
+
|
|
54
|
+
def tag
|
|
55
|
+
@parsed_tag ||= @path.tr('/', '.').squeeze('.').gsub(/^\./, '')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def register_watcher(watcher)
|
|
59
|
+
@watchers << watcher
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def detach(shutdown_start_time = nil)
|
|
63
|
+
@detached = true
|
|
64
|
+
if @io_handler
|
|
65
|
+
ready_to_shutdown(shutdown_start_time)
|
|
66
|
+
@io_handler.on_notify
|
|
67
|
+
end
|
|
68
|
+
@file_feed&.close(self)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def ready_to_shutdown(shutdown_start_time)
|
|
72
|
+
@shutdown_start_time = shutdown_start_time
|
|
73
|
+
@io_handler.ready_to_shutdown(shutdown_start_time) if @io_handler
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def close
|
|
77
|
+
if @io_handler&.pending? || @close_pending
|
|
78
|
+
@close_pending = true
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
force_close
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def force_close
|
|
86
|
+
@close_pending = false
|
|
87
|
+
if @io_handler
|
|
88
|
+
@io_handler.close
|
|
89
|
+
@io_handler = nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def defer_close
|
|
94
|
+
@close_pending = true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def close_if_ready(force: false)
|
|
98
|
+
return false unless @close_pending && @io_handler && !@io_handler.pending?
|
|
99
|
+
return false unless force || @io_handler.drained?
|
|
100
|
+
|
|
101
|
+
force_close
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def eof?
|
|
106
|
+
@io_handler.nil? || @io_handler.eof?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def drained?
|
|
110
|
+
@io_handler.nil? || @io_handler.drained?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def pending?
|
|
114
|
+
@io_handler&.pending? || false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def on_notify
|
|
118
|
+
if @io_handler&.pending?
|
|
119
|
+
@io_handler.on_notify
|
|
120
|
+
return
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
begin
|
|
124
|
+
stat = Fluent::FileWrapper.stat(@path)
|
|
125
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
126
|
+
# moved or deleted
|
|
127
|
+
stat = nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
@rotate_handler.on_notify(stat) if @rotate_handler
|
|
131
|
+
read_more
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def complete_async(success, resume: true)
|
|
135
|
+
return false unless @io_handler
|
|
136
|
+
|
|
137
|
+
should_notify = @io_handler.complete_async(success)
|
|
138
|
+
if @detached
|
|
139
|
+
read_more if resume && (should_notify || success)
|
|
140
|
+
close_if_ready
|
|
141
|
+
else
|
|
142
|
+
on_notify if resume && should_notify
|
|
143
|
+
end
|
|
144
|
+
should_notify
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def read_more
|
|
148
|
+
@file_feed&.on_notify(self)
|
|
149
|
+
@io_handler.on_notify if @io_handler
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def on_rotate(stat)
|
|
153
|
+
if @io_handler.nil?
|
|
154
|
+
if stat
|
|
155
|
+
# first time
|
|
156
|
+
fsize = stat.size
|
|
157
|
+
inode = stat.ino
|
|
158
|
+
|
|
159
|
+
last_inode = @pe.read_inode
|
|
160
|
+
if inode == last_inode
|
|
161
|
+
# rotated file has the same inode number with the last file.
|
|
162
|
+
# assuming following situation:
|
|
163
|
+
# a) file was once renamed and backed, or
|
|
164
|
+
# b) symlink or hardlink to the same file is recreated
|
|
165
|
+
# in either case of a and b, seek to the saved position
|
|
166
|
+
# c) file was once renamed, truncated and then backed
|
|
167
|
+
# in this case, consider it truncated
|
|
168
|
+
@pe.update(inode, 0) if fsize < @pe.read_pos
|
|
169
|
+
elsif last_inode != 0
|
|
170
|
+
# this is FilePositionEntry and fluentd once started.
|
|
171
|
+
# read data from the head of the rotated file.
|
|
172
|
+
# logs never duplicate because this file is a rotated new file.
|
|
173
|
+
@pe.update(inode, 0)
|
|
174
|
+
else
|
|
175
|
+
# this is MemoryPositionEntry or this is the first time fluentd started.
|
|
176
|
+
# seek to the end of the any files.
|
|
177
|
+
# logs may duplicate without this seek because it's not sure the file is
|
|
178
|
+
# existent file or rotated new file.
|
|
179
|
+
pos = @read_from_head ? 0 : fsize
|
|
180
|
+
@pe.update(inode, pos)
|
|
181
|
+
end
|
|
182
|
+
@io_handler = io_handler
|
|
183
|
+
else
|
|
184
|
+
@io_handler = NullIOHandler.new
|
|
185
|
+
end
|
|
186
|
+
else
|
|
187
|
+
watcher_needs_update = false
|
|
188
|
+
|
|
189
|
+
if stat
|
|
190
|
+
inode = stat.ino
|
|
191
|
+
if inode == @pe.read_inode # truncated
|
|
192
|
+
@pe.update_pos(0)
|
|
193
|
+
@io_handler.close
|
|
194
|
+
elsif !@io_handler.opened? # There is no previous file. Reuse TailWatcher
|
|
195
|
+
@pe.update(inode, 0)
|
|
196
|
+
else # file is rotated and new file found
|
|
197
|
+
watcher_needs_update = true
|
|
198
|
+
# Handle the old log file before renewing TailWatcher [fluentd#1055]
|
|
199
|
+
@io_handler.on_notify
|
|
200
|
+
end
|
|
201
|
+
else # file is rotated and new file not found
|
|
202
|
+
# Clear RotateHandler to avoid duplicated file watch in same path.
|
|
203
|
+
@rotate_handler = nil
|
|
204
|
+
watcher_needs_update = true
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
if watcher_needs_update
|
|
208
|
+
if @follow_inodes
|
|
209
|
+
# If stat is nil (file not present), NEED to stop and discard this watcher.
|
|
210
|
+
# When the file is disappeared but is resurrected soon, then `#refresh_watcher`
|
|
211
|
+
# can't recognize this TailWatcher needs to be stopped.
|
|
212
|
+
# This can happens when the file is rotated.
|
|
213
|
+
# If a notify comes before the new file for the path is created during rotation,
|
|
214
|
+
# then it appears as if the file was resurrected once it disappeared.
|
|
215
|
+
# Don't want to swap state because we need latest read offset in pos file even after rotate_wait
|
|
216
|
+
@update_watcher.call(self, @pe, stat&.ino)
|
|
217
|
+
else
|
|
218
|
+
# Permit to handle if stat is nil (file not present).
|
|
219
|
+
# If a file is mv-ed and a new file is created during
|
|
220
|
+
# calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
|
|
221
|
+
# and `#stop_watchers()` for the path because `target_paths_hash`
|
|
222
|
+
# always contains the path.
|
|
223
|
+
@update_watcher.call(self, swap_state(@pe), stat&.ino)
|
|
224
|
+
end
|
|
225
|
+
else
|
|
226
|
+
@log.info "detected rotation of #{@path}"
|
|
227
|
+
@io_handler = io_handler
|
|
228
|
+
end
|
|
229
|
+
@metrics.rotated.inc
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def io_handler
|
|
234
|
+
handler = @io_handler_build.call(self, @path)
|
|
235
|
+
handler.ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
|
|
236
|
+
handler
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def swap_state(pe)
|
|
240
|
+
# Use MemoryPositionEntry for rotated file temporary
|
|
241
|
+
mpe = MemoryPositionEntry.new
|
|
242
|
+
mpe.update(pe.read_inode, pe.read_pos)
|
|
243
|
+
@pe = mpe
|
|
244
|
+
pe # This pe will be updated in on_rotate after TailWatcher is initialized
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
class RotateHandler
|
|
248
|
+
def initialize(log, &on_rotate)
|
|
249
|
+
@log = log
|
|
250
|
+
@inode = nil
|
|
251
|
+
@fsize = -1 # first
|
|
252
|
+
@on_rotate = on_rotate
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def on_notify(stat)
|
|
256
|
+
if stat.nil?
|
|
257
|
+
inode = nil
|
|
258
|
+
fsize = 0
|
|
259
|
+
else
|
|
260
|
+
inode = stat.ino
|
|
261
|
+
fsize = stat.size
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
if @inode != inode || fsize < @fsize
|
|
265
|
+
@on_rotate.call(stat)
|
|
266
|
+
end
|
|
267
|
+
@inode = inode
|
|
268
|
+
@fsize = fsize
|
|
269
|
+
rescue
|
|
270
|
+
@log.error $!.to_s
|
|
271
|
+
@log.error_backtrace
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Fluentd
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
require 'cool.io'
|
|
18
|
+
require 'socket'
|
|
19
|
+
require 'fluent/env'
|
|
20
|
+
require 'fluent/plugin/input'
|
|
21
|
+
|
|
22
|
+
module Fluent::Plugin
|
|
23
|
+
class TailInput < Fluent::Plugin::Input
|
|
24
|
+
class WorkerPool
|
|
25
|
+
class WorkerStopped < StandardError; end
|
|
26
|
+
Task = Struct.new(:key, :bytes, :payload)
|
|
27
|
+
Result = Struct.new(:task, :value, :error)
|
|
28
|
+
|
|
29
|
+
def initialize(num_threads:, task_limit:, byte_limit:, thread_create:, notify:, &process)
|
|
30
|
+
unless num_threads > 0 && task_limit > 0 && byte_limit > 0
|
|
31
|
+
raise ArgumentError, 'worker and queue limits must be positive'
|
|
32
|
+
end
|
|
33
|
+
raise ArgumentError, 'processor is required' unless process
|
|
34
|
+
|
|
35
|
+
@num_threads = num_threads
|
|
36
|
+
@task_limit = task_limit
|
|
37
|
+
@byte_limit = byte_limit
|
|
38
|
+
@thread_create = thread_create
|
|
39
|
+
@notify = notify
|
|
40
|
+
@process = process
|
|
41
|
+
@mutex = Mutex.new
|
|
42
|
+
@jobs = Queue.new
|
|
43
|
+
@results = Queue.new
|
|
44
|
+
@active = {}
|
|
45
|
+
@bytes = 0
|
|
46
|
+
@regular_bytes = 0
|
|
47
|
+
@oversized_task = nil
|
|
48
|
+
@threads = []
|
|
49
|
+
@state = :new
|
|
50
|
+
@failure = nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def start
|
|
54
|
+
@mutex.synchronize do
|
|
55
|
+
raise 'worker pool already started' unless @state == :new
|
|
56
|
+
@state = :starting
|
|
57
|
+
end
|
|
58
|
+
begin
|
|
59
|
+
@num_threads.times do |index|
|
|
60
|
+
@threads << @thread_create.call(:"in_tail_worker_#{index}") { run(index) }
|
|
61
|
+
end
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
raise(@failure || WorkerStopped.new('pool stopped during start')) unless @state == :starting
|
|
64
|
+
@state = :running
|
|
65
|
+
end
|
|
66
|
+
self
|
|
67
|
+
rescue
|
|
68
|
+
stop
|
|
69
|
+
join
|
|
70
|
+
raise
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Capacity remains reserved until emit and position handling acknowledge
|
|
75
|
+
# the result, including while a parsed batch waits in the result queue.
|
|
76
|
+
def submit(key, payload, bytes:)
|
|
77
|
+
raise ArgumentError, 'batch bytes must be nonnegative' if bytes < 0
|
|
78
|
+
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
return false unless @state == :running
|
|
81
|
+
return false if @active.key?(key) || @active.size >= @task_limit
|
|
82
|
+
# A single over-limit batch may use an extra reservation alongside the
|
|
83
|
+
# bounded regular budget so it does not force parsing on the event loop.
|
|
84
|
+
if bytes > @byte_limit
|
|
85
|
+
return false if @oversized_task
|
|
86
|
+
else
|
|
87
|
+
return false if @regular_bytes + bytes > @byte_limit
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
task = Task.new(key, bytes, payload).freeze
|
|
91
|
+
@active[key] = task
|
|
92
|
+
@bytes += bytes
|
|
93
|
+
if bytes > @byte_limit
|
|
94
|
+
@oversized_task = task
|
|
95
|
+
else
|
|
96
|
+
@regular_bytes += bytes
|
|
97
|
+
end
|
|
98
|
+
@jobs << task
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def next_result
|
|
104
|
+
@results.pop(true)
|
|
105
|
+
rescue ThreadError
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def acknowledge(result)
|
|
110
|
+
@mutex.synchronize do
|
|
111
|
+
task = result.task
|
|
112
|
+
unless @active[task.key].equal?(task)
|
|
113
|
+
raise ArgumentError, 'result is no longer active'
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
@active.delete(task.key)
|
|
117
|
+
@bytes -= task.bytes
|
|
118
|
+
if task.bytes > @byte_limit
|
|
119
|
+
@oversized_task = nil
|
|
120
|
+
else
|
|
121
|
+
@regular_bytes -= task.bytes
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def statistics
|
|
127
|
+
@mutex.synchronize { { tasks: @active.size, bytes: @bytes, state: @state }.freeze }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def failure
|
|
131
|
+
@mutex.synchronize { @failure }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def stop
|
|
135
|
+
@mutex.synchronize do
|
|
136
|
+
@state = :stopping unless @state == :failed
|
|
137
|
+
@jobs.close
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The source joins after draining results; the event loop never joins.
|
|
142
|
+
def join(timeout: nil)
|
|
143
|
+
deadline = timeout && Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
144
|
+
@threads.each do |thread|
|
|
145
|
+
remaining = deadline && [deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
|
146
|
+
return false unless thread.join(remaining)
|
|
147
|
+
end
|
|
148
|
+
true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def kill
|
|
152
|
+
@threads.each(&:kill)
|
|
153
|
+
@threads.each(&:join)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def run(index)
|
|
159
|
+
normal_exit = false
|
|
160
|
+
while (task = @jobs.pop)
|
|
161
|
+
value = nil
|
|
162
|
+
error = nil
|
|
163
|
+
completed = false
|
|
164
|
+
begin
|
|
165
|
+
value = @process.call(index, task.payload)
|
|
166
|
+
completed = true
|
|
167
|
+
rescue Exception => e
|
|
168
|
+
# Deliver task failures to the owner instead of losing an active
|
|
169
|
+
# file or terminating a helper thread with abort_on_exception.
|
|
170
|
+
error = e
|
|
171
|
+
completed = true
|
|
172
|
+
ensure
|
|
173
|
+
error = WorkerStopped.new('worker stopped during a task') unless completed
|
|
174
|
+
publish(Result.new(task, value, error).freeze)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
normal_exit = true
|
|
178
|
+
ensure
|
|
179
|
+
fail_pool(WorkerStopped.new('worker exited unexpectedly')) unless normal_exit
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def publish(result)
|
|
183
|
+
@results << result
|
|
184
|
+
begin
|
|
185
|
+
@notify.call
|
|
186
|
+
rescue Exception => error
|
|
187
|
+
fail_pool(error)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def fail_pool(error)
|
|
192
|
+
@mutex.synchronize do
|
|
193
|
+
@failure ||= error
|
|
194
|
+
@state = :failed
|
|
195
|
+
@jobs.close
|
|
196
|
+
end
|
|
197
|
+
begin
|
|
198
|
+
while (task = @jobs.pop(true))
|
|
199
|
+
@results << Result.new(task, nil, error).freeze
|
|
200
|
+
end
|
|
201
|
+
rescue ThreadError
|
|
202
|
+
# All queued jobs have been accounted for.
|
|
203
|
+
end
|
|
204
|
+
begin
|
|
205
|
+
@notify.call
|
|
206
|
+
rescue Exception => notification_error
|
|
207
|
+
@mutex.synchronize { @failure ||= notification_error }
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Captures the built-in hook implementations once and checks whether a
|
|
213
|
+
# TailInput instance resolves each hook to the same method body. A mismatch
|
|
214
|
+
# means worker execution must fall back to the synchronous compatibility
|
|
215
|
+
# path; this deliberately does not try to infer safety from method owners.
|
|
216
|
+
class WorkerCompatibility
|
|
217
|
+
HOOKS = %i[
|
|
218
|
+
receive_lines flush_buffer convert_line_to_event parse_singleline
|
|
219
|
+
parse_multilines setup_watcher io_handler build_line_feeder
|
|
220
|
+
].freeze
|
|
221
|
+
|
|
222
|
+
def initialize(base_class)
|
|
223
|
+
@hooks = HOOKS.to_h { |name| [name, base_class.instance_method(name)] }.freeze
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def compatible?(plugin)
|
|
227
|
+
incompatible_hooks(plugin).empty?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def incompatible_hooks(plugin)
|
|
231
|
+
@hooks.filter_map do |name, built_in|
|
|
232
|
+
name unless plugin.singleton_class.instance_method(name) == built_in
|
|
233
|
+
rescue NameError
|
|
234
|
+
name
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
class WorkerCompletionWatcher < Coolio::IOWatcher
|
|
240
|
+
def initialize(log: nil, &on_completion)
|
|
241
|
+
@log = log
|
|
242
|
+
if Fluent.windows?
|
|
243
|
+
# Winsock select cannot monitor IO.pipe handles. Use connected
|
|
244
|
+
# loopback sockets, as libev does for its own Windows wakeups.
|
|
245
|
+
begin
|
|
246
|
+
listener = TCPServer.new('127.0.0.1', 0)
|
|
247
|
+
@writer = TCPSocket.new('127.0.0.1', listener.addr[1])
|
|
248
|
+
@reader = listener.accept
|
|
249
|
+
rescue
|
|
250
|
+
@reader&.close
|
|
251
|
+
@writer&.close
|
|
252
|
+
raise
|
|
253
|
+
ensure
|
|
254
|
+
listener&.close
|
|
255
|
+
end
|
|
256
|
+
else
|
|
257
|
+
@reader, @writer = IO.pipe
|
|
258
|
+
end
|
|
259
|
+
super(@reader)
|
|
260
|
+
@on_completion = on_completion
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def signal
|
|
264
|
+
@writer.write_nonblock("\0")
|
|
265
|
+
true
|
|
266
|
+
rescue IO::WaitWritable
|
|
267
|
+
# A full pipe already has a notification pending on the event loop.
|
|
268
|
+
false
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def on_signal
|
|
272
|
+
@on_completion.call
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def on_readable
|
|
276
|
+
notified = false
|
|
277
|
+
begin
|
|
278
|
+
loop do
|
|
279
|
+
@reader.read_nonblock(4096)
|
|
280
|
+
notified = true
|
|
281
|
+
end
|
|
282
|
+
rescue IO::WaitReadable
|
|
283
|
+
return unless notified
|
|
284
|
+
end
|
|
285
|
+
on_signal
|
|
286
|
+
rescue => error
|
|
287
|
+
@log&.error 'unexpected error while processing in_tail worker completions', error: error
|
|
288
|
+
@log&.error_backtrace
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def close
|
|
292
|
+
detach if attached?
|
|
293
|
+
@reader.close unless @reader.closed?
|
|
294
|
+
@writer.close unless @writer.closed?
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|