fluentd 0.14.9 → 0.14.10
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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/ChangeLog +44 -0
- data/appveyor.yml +1 -0
- data/code-of-conduct.md +3 -0
- data/fluentd.gemspec +1 -1
- data/lib/fluent/command/cat.rb +11 -3
- data/lib/fluent/compat/output.rb +6 -3
- data/lib/fluent/compat/parser.rb +2 -0
- data/lib/fluent/config/section.rb +1 -1
- data/lib/fluent/env.rb +1 -1
- data/lib/fluent/plugin/filter_record_transformer.rb +12 -30
- data/lib/fluent/plugin/in_forward.rb +50 -169
- data/lib/fluent/plugin/in_monitor_agent.rb +8 -4
- data/lib/fluent/plugin/in_syslog.rb +13 -7
- data/lib/fluent/plugin/in_tail.rb +29 -14
- data/lib/fluent/plugin/in_tcp.rb +54 -14
- data/lib/fluent/plugin/in_udp.rb +49 -13
- data/lib/fluent/plugin/out_file.rb +30 -14
- data/lib/fluent/plugin/out_forward.rb +199 -173
- data/lib/fluent/plugin/output.rb +71 -46
- data/lib/fluent/plugin/parser_json.rb +1 -1
- data/lib/fluent/plugin_helper.rb +2 -0
- data/lib/fluent/plugin_helper/event_loop.rb +24 -6
- data/lib/fluent/plugin_helper/inject.rb +12 -1
- data/lib/fluent/plugin_helper/server.rb +494 -0
- data/lib/fluent/plugin_helper/socket.rb +101 -0
- data/lib/fluent/plugin_helper/socket_option.rb +84 -0
- data/lib/fluent/plugin_helper/timer.rb +1 -0
- data/lib/fluent/test/driver/base.rb +45 -13
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +1 -1
- data/test/compat/test_parser.rb +10 -0
- data/test/config/test_configurable.rb +20 -0
- data/test/helper.rb +36 -1
- data/test/plugin/test_filter_record_transformer.rb +31 -103
- data/test/plugin/test_in_forward.rb +13 -75
- data/test/plugin/test_in_monitor_agent.rb +65 -35
- data/test/plugin/test_in_syslog.rb +39 -3
- data/test/plugin/test_in_tcp.rb +78 -62
- data/test/plugin/test_in_udp.rb +101 -80
- data/test/plugin/test_out_file.rb +17 -0
- data/test/plugin/test_out_forward.rb +155 -125
- data/test/plugin/test_output_as_buffered.rb +4 -2
- data/test/plugin_helper/test_inject.rb +21 -0
- data/test/plugin_helper/test_server.rb +905 -0
- data/test/test_event_time.rb +3 -1
- data/test/test_output.rb +30 -1
- data/test/test_test_drivers.rb +5 -2
- metadata +19 -6
data/lib/fluent/plugin/output.rb
CHANGED
@@ -40,6 +40,8 @@ module Fluent
|
|
40
40
|
|
41
41
|
CHUNKING_FIELD_WARN_NUM = 4
|
42
42
|
|
43
|
+
PROCESS_CLOCK_ID = Process::CLOCK_MONOTONIC_RAW rescue Process::CLOCK_MONOTONIC
|
44
|
+
|
43
45
|
config_param :time_as_integer, :bool, default: false
|
44
46
|
|
45
47
|
# `<buffer>` and `<secondary>` sections are available only when '#format' and '#write' are implemented
|
@@ -138,7 +140,7 @@ module Fluent
|
|
138
140
|
end
|
139
141
|
|
140
142
|
# Internal states
|
141
|
-
FlushThreadState = Struct.new(:thread, :
|
143
|
+
FlushThreadState = Struct.new(:thread, :next_clock)
|
142
144
|
DequeuedChunkInfo = Struct.new(:chunk_id, :time, :timeout) do
|
143
145
|
def expired?
|
144
146
|
time + timeout < Time.now
|
@@ -898,9 +900,9 @@ module Fluent
|
|
898
900
|
@retry_mutex.synchronize do
|
899
901
|
if @retry # success to flush chunks in retries
|
900
902
|
if secondary
|
901
|
-
log.warn "retry succeeded by secondary.",
|
903
|
+
log.warn "retry succeeded by secondary.", chunk_id: dump_unique_id_hex(chunk_id)
|
902
904
|
else
|
903
|
-
log.warn "retry succeeded.",
|
905
|
+
log.warn "retry succeeded.", chunk_id: dump_unique_id_hex(chunk_id)
|
904
906
|
end
|
905
907
|
@retry = nil
|
906
908
|
end
|
@@ -918,6 +920,8 @@ module Fluent
|
|
918
920
|
# in many cases, false can be just ignored
|
919
921
|
if @buffer.takeback_chunk(chunk_id)
|
920
922
|
@counters_monitor.synchronize{ @rollback_count += 1 }
|
923
|
+
primary = @as_secondary ? @primary_instance : self
|
924
|
+
primary.update_retry_state(chunk_id, @as_secondary)
|
921
925
|
true
|
922
926
|
else
|
923
927
|
false
|
@@ -930,7 +934,9 @@ module Fluent
|
|
930
934
|
info = @dequeued_chunks.shift
|
931
935
|
if @buffer.takeback_chunk(info.chunk_id)
|
932
936
|
@counters_monitor.synchronize{ @rollback_count += 1 }
|
933
|
-
log.warn "failed to flush the buffer chunk, timeout to commit.",
|
937
|
+
log.warn "failed to flush the buffer chunk, timeout to commit.", chunk_id: dump_unique_id_hex(info.chunk_id), flushed_at: info.time
|
938
|
+
primary = @as_secondary ? @primary_instance : self
|
939
|
+
primary.update_retry_state(info.chunk_id, @as_secondary)
|
934
940
|
end
|
935
941
|
end
|
936
942
|
end
|
@@ -943,7 +949,9 @@ module Fluent
|
|
943
949
|
info = @dequeued_chunks.shift
|
944
950
|
if @buffer.takeback_chunk(info.chunk_id)
|
945
951
|
@counters_monitor.synchronize{ @rollback_count += 1 }
|
946
|
-
log.info "delayed commit for buffer chunks was cancelled in shutdown",
|
952
|
+
log.info "delayed commit for buffer chunks was cancelled in shutdown", chunk_id: dump_unique_id_hex(info.chunk_id)
|
953
|
+
primary = @as_secondary ? @primary_instance : self
|
954
|
+
primary.update_retry_state(info.chunk_id, @as_secondary)
|
947
955
|
end
|
948
956
|
end
|
949
957
|
end
|
@@ -997,7 +1005,7 @@ module Fluent
|
|
997
1005
|
log.trace "done to commit a chunk", chunk: dump_chunk_id
|
998
1006
|
end
|
999
1007
|
rescue => e
|
1000
|
-
log.debug "taking back chunk for errors.",
|
1008
|
+
log.debug "taking back chunk for errors.", chunk: dump_unique_id_hex(chunk.unique_id)
|
1001
1009
|
if output.delayed_commit
|
1002
1010
|
@dequeued_chunks_mutex.synchronize do
|
1003
1011
|
@dequeued_chunks.delete_if{|d| d.chunk_id == chunk.unique_id }
|
@@ -1005,35 +1013,52 @@ module Fluent
|
|
1005
1013
|
end
|
1006
1014
|
@buffer.takeback_chunk(chunk.unique_id)
|
1007
1015
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1016
|
+
update_retry_state(chunk.unique_id, using_secondary, e)
|
1017
|
+
|
1018
|
+
raise if @under_plugin_development && !@retry_for_error_chunk
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
def update_retry_state(chunk_id, using_secondary, error = nil)
|
1023
|
+
@retry_mutex.synchronize do
|
1024
|
+
@counters_monitor.synchronize{ @num_errors += 1 }
|
1025
|
+
chunk_id_hex = dump_unique_id_hex(chunk_id)
|
1026
|
+
|
1027
|
+
unless @retry
|
1028
|
+
@retry = retry_state(@buffer_config.retry_randomize)
|
1029
|
+
if error
|
1030
|
+
log.warn "failed to flush the buffer.", retry_time: @retry.steps, next_retry_seconds: @retry.next_time, chunk: chunk_id_hex, error: error
|
1031
|
+
log.warn_backtrace error.backtrace
|
1032
|
+
end
|
1033
|
+
return
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
# @retry exists
|
1037
|
+
|
1038
|
+
if error
|
1039
|
+
if @retry.limit?
|
1040
|
+
records = @buffer.queued_records
|
1041
|
+
msg = "failed to flush the buffer, and hit limit for retries. dropping all chunks in the buffer queue."
|
1042
|
+
log.error msg, retry_times: @retry.steps, records: records, error: error
|
1043
|
+
log.error_backtrace error.backtrace
|
1044
|
+
elsif using_secondary
|
1045
|
+
msg = "failed to flush the buffer with secondary output."
|
1046
|
+
log.warn msg, retry_time: @retry.steps, next_retry_seconds: @retry.next_time, chunk: chunk_id_hex, error: error
|
1047
|
+
log.warn_backtrace error.backtrace
|
1028
1048
|
else
|
1029
|
-
|
1030
|
-
@
|
1031
|
-
log.
|
1032
|
-
log.warn_backtrace e.backtrace
|
1049
|
+
msg = "failed to flush the buffer."
|
1050
|
+
log.warn msg, retry_time: @retry.steps, next_retry_seconds: @retry.next_time, chunk: chunk_id_hex, error: error
|
1051
|
+
log.warn_backtrace error.backtrace
|
1033
1052
|
end
|
1034
1053
|
end
|
1035
1054
|
|
1036
|
-
|
1055
|
+
if @retry.limit?
|
1056
|
+
@buffer.clear_queue!
|
1057
|
+
log.debug "buffer queue cleared"
|
1058
|
+
@retry = nil
|
1059
|
+
else
|
1060
|
+
@retry.step
|
1061
|
+
end
|
1037
1062
|
end
|
1038
1063
|
end
|
1039
1064
|
|
@@ -1060,7 +1085,7 @@ module Fluent
|
|
1060
1085
|
# Without locks: it is rough but enough to select "next" writer selection
|
1061
1086
|
@output_flush_thread_current_position = (@output_flush_thread_current_position + 1) % @buffer_config.flush_thread_count
|
1062
1087
|
state = @output_flush_threads[@output_flush_thread_current_position]
|
1063
|
-
state.
|
1088
|
+
state.next_clock = 0
|
1064
1089
|
if state.thread && state.thread.status # "run"/"sleep"/"aborting" or false(successfully stop) or nil(killed by exception)
|
1065
1090
|
state.thread.run
|
1066
1091
|
else
|
@@ -1102,7 +1127,7 @@ module Fluent
|
|
1102
1127
|
# only for tests of output plugin
|
1103
1128
|
def flush_thread_wakeup
|
1104
1129
|
@output_flush_threads.each do |state|
|
1105
|
-
state.
|
1130
|
+
state.next_clock = 0
|
1106
1131
|
state.thread.run
|
1107
1132
|
end
|
1108
1133
|
end
|
@@ -1156,7 +1181,7 @@ module Fluent
|
|
1156
1181
|
end
|
1157
1182
|
rescue => e
|
1158
1183
|
raise if @under_plugin_development
|
1159
|
-
log.error "unexpected error while checking flushed chunks. ignored.",
|
1184
|
+
log.error "unexpected error while checking flushed chunks. ignored.", error: e
|
1160
1185
|
log.error_backtrace
|
1161
1186
|
ensure
|
1162
1187
|
@output_enqueue_thread_waiting = false
|
@@ -1166,7 +1191,7 @@ module Fluent
|
|
1166
1191
|
end
|
1167
1192
|
rescue => e
|
1168
1193
|
# normal errors are rescued by inner begin-rescue clause.
|
1169
|
-
log.error "error on enqueue thread",
|
1194
|
+
log.error "error on enqueue thread", error: e
|
1170
1195
|
log.error_backtrace
|
1171
1196
|
raise
|
1172
1197
|
end
|
@@ -1175,9 +1200,7 @@ module Fluent
|
|
1175
1200
|
def flush_thread_run(state)
|
1176
1201
|
flush_thread_interval = @buffer_config.flush_thread_interval
|
1177
1202
|
|
1178
|
-
|
1179
|
-
clock_id = Process::CLOCK_MONOTONIC rescue Process::CLOCK_MONOTONIC_RAW
|
1180
|
-
state.next_time = Process.clock_gettime(clock_id) + flush_thread_interval
|
1203
|
+
state.next_clock = Process.clock_gettime(PROCESS_CLOCK_ID) + flush_thread_interval
|
1181
1204
|
|
1182
1205
|
while !self.after_started? && !self.stopped?
|
1183
1206
|
sleep 0.5
|
@@ -1187,16 +1210,18 @@ module Fluent
|
|
1187
1210
|
begin
|
1188
1211
|
# This thread don't use `thread_current_running?` because this thread should run in `before_shutdown` phase
|
1189
1212
|
while @output_flush_threads_running
|
1190
|
-
|
1191
|
-
interval = state.
|
1213
|
+
current_clock = Process.clock_gettime(PROCESS_CLOCK_ID)
|
1214
|
+
interval = state.next_clock - current_clock
|
1192
1215
|
|
1193
|
-
if state.next_time <=
|
1216
|
+
if state.next_clock <= current_clock && (!@retry || @retry_mutex.synchronize{ @retry.next_time } <= Time.now)
|
1194
1217
|
try_flush
|
1195
|
-
|
1218
|
+
|
1219
|
+
# next_flush_time uses flush_thread_interval or flush_thread_burst_interval (or retrying)
|
1196
1220
|
interval = next_flush_time.to_f - Time.now.to_f
|
1197
|
-
# TODO: if secondary && delayed-commit, next_flush_time will be much longer than expected
|
1198
|
-
#
|
1199
|
-
|
1221
|
+
# TODO: if secondary && delayed-commit, next_flush_time will be much longer than expected
|
1222
|
+
# because @retry still exists (#commit_write is not called yet in #try_flush)
|
1223
|
+
# @retry should be cleared if delayed commit is enabled? Or any other solution?
|
1224
|
+
state.next_clock = Process.clock_gettime(PROCESS_CLOCK_ID) + interval
|
1200
1225
|
end
|
1201
1226
|
|
1202
1227
|
if @dequeued_chunks_mutex.synchronize{ !@dequeued_chunks.empty? && @dequeued_chunks.first.expired? }
|
@@ -1210,7 +1235,7 @@ module Fluent
|
|
1210
1235
|
rescue => e
|
1211
1236
|
# normal errors are rescued by output plugins in #try_flush
|
1212
1237
|
# so this rescue section is for critical & unrecoverable errors
|
1213
|
-
log.error "error on output thread",
|
1238
|
+
log.error "error on output thread", error: e
|
1214
1239
|
log.error_backtrace
|
1215
1240
|
raise
|
1216
1241
|
end
|
data/lib/fluent/plugin_helper.rb
CHANGED
@@ -24,6 +24,8 @@ require 'fluent/plugin_helper/parser'
|
|
24
24
|
require 'fluent/plugin_helper/formatter'
|
25
25
|
require 'fluent/plugin_helper/inject'
|
26
26
|
require 'fluent/plugin_helper/extract'
|
27
|
+
require 'fluent/plugin_helper/socket'
|
28
|
+
require 'fluent/plugin_helper/server'
|
27
29
|
require 'fluent/plugin_helper/retry_state'
|
28
30
|
require 'fluent/plugin_helper/compat_parameters'
|
29
31
|
|
@@ -30,12 +30,16 @@ module Fluent
|
|
30
30
|
# terminate: initialize internal state
|
31
31
|
|
32
32
|
EVENT_LOOP_RUN_DEFAULT_TIMEOUT = 0.5
|
33
|
+
EVENT_LOOP_SHUTDOWN_TIMEOUT = 5
|
34
|
+
EVENT_LOOP_CLOCK_ID = Process::CLOCK_MONOTONIC_RAW rescue Process::CLOCK_MONOTONIC
|
33
35
|
|
34
36
|
attr_reader :_event_loop # for tests
|
35
37
|
|
36
38
|
def event_loop_attach(watcher)
|
37
39
|
@_event_loop_mutex.synchronize do
|
38
40
|
@_event_loop.attach(watcher)
|
41
|
+
@_event_loop_attached_watchers << watcher
|
42
|
+
watcher
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -58,6 +62,7 @@ module Fluent
|
|
58
62
|
@_event_loop_mutex = Mutex.new
|
59
63
|
# plugin MAY configure loop run timeout in #configure
|
60
64
|
@_event_loop_run_timeout = EVENT_LOOP_RUN_DEFAULT_TIMEOUT
|
65
|
+
@_event_loop_attached_watchers = []
|
61
66
|
end
|
62
67
|
|
63
68
|
def start
|
@@ -65,19 +70,32 @@ module Fluent
|
|
65
70
|
|
66
71
|
# event loop does not run here, so mutex lock is not required
|
67
72
|
thread_create :event_loop do
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
+
begin
|
74
|
+
default_watcher = DefaultWatcher.new
|
75
|
+
event_loop_attach(default_watcher)
|
76
|
+
@_event_loop_running = true
|
77
|
+
@_event_loop.run(@_event_loop_run_timeout) # this method blocks
|
78
|
+
ensure
|
79
|
+
@_event_loop_running = false
|
80
|
+
end
|
73
81
|
end
|
74
82
|
end
|
75
83
|
|
76
84
|
def shutdown
|
77
85
|
@_event_loop_mutex.synchronize do
|
78
|
-
@
|
86
|
+
@_event_loop_attached_watchers.reverse.each do |w|
|
87
|
+
if w.attached?
|
88
|
+
w.detach
|
89
|
+
end
|
90
|
+
end
|
79
91
|
end
|
92
|
+
timeout_at = Process.clock_gettime(EVENT_LOOP_CLOCK_ID) + EVENT_LOOP_SHUTDOWN_TIMEOUT
|
80
93
|
while @_event_loop_running
|
94
|
+
if Process.clock_gettime(EVENT_LOOP_CLOCK_ID) >= timeout_at
|
95
|
+
log.warn "event loop does NOT exit until hard timeout."
|
96
|
+
raise "event loop does NOT exit until hard timeout." if @under_plugin_development
|
97
|
+
break
|
98
|
+
end
|
81
99
|
sleep 0.1
|
82
100
|
end
|
83
101
|
|
@@ -97,9 +97,20 @@ module Fluent
|
|
97
97
|
if @inject_config
|
98
98
|
@_inject_hostname_key = @inject_config.hostname_key
|
99
99
|
if @_inject_hostname_key
|
100
|
+
if self.respond_to?(:buffer_config)
|
101
|
+
# Output plugin cannot use "hostname"(specified by @hostname_key),
|
102
|
+
# injected by this plugin helper, in chunk keys.
|
103
|
+
# This plugin helper works in `#format` (in many cases), but modified record
|
104
|
+
# don't have any side effect in chunking of output plugin.
|
105
|
+
if self.buffer_config.chunk_keys.include?(@_inject_hostname_key)
|
106
|
+
log.error "Use filters to inject hostname to use it in buffer chunking."
|
107
|
+
raise Fluent::ConfigError, "the key specified by 'hostname_key' in <inject> cannot be used in buffering chunk key."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
100
111
|
@_inject_hostname = @inject_config.hostname
|
101
112
|
unless @_inject_hostname
|
102
|
-
@_inject_hostname = Socket.gethostname
|
113
|
+
@_inject_hostname = ::Socket.gethostname
|
103
114
|
log.info "using hostname for specified field", host_key: @_inject_hostname_key, host_name: @_inject_hostname
|
104
115
|
end
|
105
116
|
end
|
@@ -0,0 +1,494 @@
|
|
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_helper/event_loop'
|
18
|
+
|
19
|
+
require 'serverengine'
|
20
|
+
require 'cool.io'
|
21
|
+
require 'socket'
|
22
|
+
require 'ipaddr'
|
23
|
+
require 'fcntl'
|
24
|
+
|
25
|
+
require_relative 'socket_option'
|
26
|
+
|
27
|
+
module Fluent
|
28
|
+
module PluginHelper
|
29
|
+
module Server
|
30
|
+
include Fluent::PluginHelper::EventLoop
|
31
|
+
include Fluent::PluginHelper::SocketOption
|
32
|
+
|
33
|
+
# This plugin helper doesn't support these things for now:
|
34
|
+
# * SSL/TLS (TBD)
|
35
|
+
# * TCP/TLS keepalive
|
36
|
+
|
37
|
+
# stop : [-]
|
38
|
+
# shutdown : detach server event handler from event loop (event_loop)
|
39
|
+
# close : close listening sockets
|
40
|
+
# terminate: remote all server instances
|
41
|
+
|
42
|
+
attr_reader :_servers # for tests
|
43
|
+
|
44
|
+
def server_wait_until_start
|
45
|
+
# event_loop_wait_until_start works well for this
|
46
|
+
end
|
47
|
+
|
48
|
+
def server_wait_until_stop
|
49
|
+
sleep 0.1 while @_servers.any?{|si| si.server.attached? }
|
50
|
+
@_servers.each{|si| si.server.close rescue nil }
|
51
|
+
end
|
52
|
+
|
53
|
+
PROTOCOLS = [:tcp, :udp, :tls, :unix]
|
54
|
+
CONNECTION_PROTOCOLS = [:tcp, :tls, :unix]
|
55
|
+
|
56
|
+
# server_create_connection(:title, @port) do |conn|
|
57
|
+
# # on connection
|
58
|
+
# source_addr = conn.remote_host
|
59
|
+
# source_port = conn.remote_port
|
60
|
+
# conn.data do |data|
|
61
|
+
# # on data
|
62
|
+
# conn.write resp # ...
|
63
|
+
# conn.close
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
def server_create_connection(title, port, proto: :tcp, bind: '0.0.0.0', shared: true, backlog: nil, **socket_options, &block)
|
67
|
+
raise ArgumentError, "BUG: title must be a symbol" unless title && title.is_a?(Symbol)
|
68
|
+
raise ArgumentError, "BUG: port must be an integer" unless port && port.is_a?(Integer)
|
69
|
+
raise ArgumentError, "BUG: invalid protocol name" unless PROTOCOLS.include?(proto)
|
70
|
+
raise ArgumentError, "BUG: cannot create connection for UDP" unless CONNECTION_PROTOCOLS.include?(proto)
|
71
|
+
|
72
|
+
raise ArgumentError, "BUG: block not specified which handles connection" unless block_given?
|
73
|
+
raise ArgumentError, "BUG: block must have just one argument" unless block.arity == 1
|
74
|
+
|
75
|
+
if proto == :tcp || proto == :tls # default linger_timeout only for server
|
76
|
+
socket_options[:linger_timeout] ||= 0
|
77
|
+
end
|
78
|
+
|
79
|
+
socket_option_validate!(proto, **socket_options)
|
80
|
+
socket_option_setter = ->(sock){ socket_option_set(sock, **socket_options) }
|
81
|
+
|
82
|
+
case proto
|
83
|
+
when :tcp
|
84
|
+
server = server_create_for_tcp_connection(shared, bind, port, backlog, socket_option_setter, &block)
|
85
|
+
when :tls
|
86
|
+
raise ArgumentError, "BUG: certopts (certificate options) not specified for TLS" unless certopts
|
87
|
+
# server_certopts_validate!(certopts)
|
88
|
+
# sock = server_create_tls_socket(shared, bind, port)
|
89
|
+
# server = nil # ...
|
90
|
+
raise "not implemented yet"
|
91
|
+
when :unix
|
92
|
+
raise "not implemented yet"
|
93
|
+
else
|
94
|
+
raise "unknown protocol #{proto}"
|
95
|
+
end
|
96
|
+
|
97
|
+
server_attach(title, proto, port, bind, shared, server)
|
98
|
+
end
|
99
|
+
|
100
|
+
# server_create(:title, @port) do |data|
|
101
|
+
# # ...
|
102
|
+
# end
|
103
|
+
# server_create(:title, @port) do |data, conn|
|
104
|
+
# # ...
|
105
|
+
# end
|
106
|
+
# server_create(:title, @port, proto: :udp, max_bytes: 2048) do |data, sock|
|
107
|
+
# sock.remote_host
|
108
|
+
# sock.remote_port
|
109
|
+
# # ...
|
110
|
+
# end
|
111
|
+
def server_create(title, port, proto: :tcp, bind: '0.0.0.0', shared: true, socket: nil, backlog: nil, max_bytes: nil, flags: 0, **socket_options, &callback)
|
112
|
+
raise ArgumentError, "BUG: title must be a symbol" unless title && title.is_a?(Symbol)
|
113
|
+
raise ArgumentError, "BUG: port must be an integer" unless port && port.is_a?(Integer)
|
114
|
+
raise ArgumentError, "BUG: invalid protocol name" unless PROTOCOLS.include?(proto)
|
115
|
+
|
116
|
+
raise ArgumentError, "BUG: socket option is available only for udp" if socket && proto != :udp
|
117
|
+
|
118
|
+
raise ArgumentError, "BUG: block not specified which handles received data" unless block_given?
|
119
|
+
raise ArgumentError, "BUG: block must have 1 or 2 arguments" unless callback.arity == 1 || callback.arity == 2
|
120
|
+
|
121
|
+
if proto == :tcp || proto == :tls # default linger_timeout only for server
|
122
|
+
socket_options[:linger_timeout] ||= 0
|
123
|
+
end
|
124
|
+
|
125
|
+
unless socket
|
126
|
+
socket_option_validate!(proto, **socket_options)
|
127
|
+
socket_option_setter = ->(sock){ socket_option_set(sock, **socket_options) }
|
128
|
+
end
|
129
|
+
|
130
|
+
if proto != :tcp && proto != :tls && proto != :unix # options to listen/accept connections
|
131
|
+
raise ArgumentError, "BUG: backlog is available for tcp/tls" if backlog
|
132
|
+
end
|
133
|
+
if proto != :udp # UDP options
|
134
|
+
raise ArgumentError, "BUG: max_bytes is available only for udp" if max_bytes
|
135
|
+
raise ArgumentError, "BUG: flags is available only for udp" if flags != 0
|
136
|
+
end
|
137
|
+
|
138
|
+
case proto
|
139
|
+
when :tcp
|
140
|
+
server = server_create_for_tcp_connection(shared, bind, port, backlog, socket_option_setter) do |conn|
|
141
|
+
conn.data(&callback)
|
142
|
+
end
|
143
|
+
when :tls
|
144
|
+
raise "not implemented yet"
|
145
|
+
when :udp
|
146
|
+
raise ArgumentError, "BUG: max_bytes must be specified for UDP" unless max_bytes
|
147
|
+
if socket
|
148
|
+
sock = socket
|
149
|
+
close_socket = false
|
150
|
+
else
|
151
|
+
sock = server_create_udp_socket(shared, bind, port)
|
152
|
+
socket_option_setter.call(sock)
|
153
|
+
close_socket = true
|
154
|
+
end
|
155
|
+
server = EventHandler::UDPServer.new(sock, max_bytes, flags, close_socket, @log, @under_plugin_development, &callback)
|
156
|
+
when :unix
|
157
|
+
raise "not implemented yet"
|
158
|
+
else
|
159
|
+
raise "BUG: unknown protocol #{proto}"
|
160
|
+
end
|
161
|
+
|
162
|
+
server_attach(title, proto, port, bind, shared, server)
|
163
|
+
end
|
164
|
+
|
165
|
+
def server_create_tcp(title, port, **kwargs, &callback)
|
166
|
+
server_create(title, port, proto: :tcp, **kwargs, &callback)
|
167
|
+
end
|
168
|
+
|
169
|
+
def server_create_udp(title, port, **kwargs, &callback)
|
170
|
+
server_create(title, port, proto: :udp, **kwargs, &callback)
|
171
|
+
end
|
172
|
+
|
173
|
+
def server_create_tls(title, port, **kwargs, &callback)
|
174
|
+
server_create(title, port, proto: :tls, **kwargs, &callback)
|
175
|
+
end
|
176
|
+
|
177
|
+
def server_create_unix(title, port, **kwargs, &callback)
|
178
|
+
server_create(title, port, proto: :unix, **kwargs, &callback)
|
179
|
+
end
|
180
|
+
|
181
|
+
ServerInfo = Struct.new(:title, :proto, :port, :bind, :shared, :server)
|
182
|
+
|
183
|
+
def server_attach(title, proto, port, bind, shared, server)
|
184
|
+
@_servers << ServerInfo.new(title, proto, port, bind, shared, server)
|
185
|
+
event_loop_attach(server)
|
186
|
+
end
|
187
|
+
|
188
|
+
def server_create_for_tcp_connection(shared, bind, port, backlog, socket_option_setter, &block)
|
189
|
+
sock = server_create_tcp_socket(shared, bind, port)
|
190
|
+
socket_option_setter.call(sock)
|
191
|
+
close_callback = ->(conn){ @_server_mutex.synchronize{ @_server_connections.delete(conn) } }
|
192
|
+
server = Coolio::TCPServer.new(sock, nil, EventHandler::TCPServer, socket_option_setter, close_callback, @log, @under_plugin_development, block) do |conn|
|
193
|
+
@_server_mutex.synchronize do
|
194
|
+
@_server_connections << conn
|
195
|
+
end
|
196
|
+
end
|
197
|
+
server.listen(backlog) if backlog
|
198
|
+
server
|
199
|
+
end
|
200
|
+
|
201
|
+
def initialize
|
202
|
+
super
|
203
|
+
@_servers = []
|
204
|
+
@_server_connections = []
|
205
|
+
@_server_mutex = Mutex.new
|
206
|
+
end
|
207
|
+
|
208
|
+
def shutdown
|
209
|
+
@_server_connections.each do |conn|
|
210
|
+
conn.close rescue nil
|
211
|
+
end
|
212
|
+
@_server_mutex.synchronize do
|
213
|
+
@_servers.each do |si|
|
214
|
+
si.server.detach if si.server.attached?
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
super
|
219
|
+
end
|
220
|
+
|
221
|
+
def close
|
222
|
+
@_server_connections.each do |conn|
|
223
|
+
conn.close rescue nil
|
224
|
+
end
|
225
|
+
@_server_mutex.synchronize do
|
226
|
+
@_servers.each do |si|
|
227
|
+
si.server.close rescue nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
super
|
231
|
+
end
|
232
|
+
|
233
|
+
def terminate
|
234
|
+
@_servers = []
|
235
|
+
super
|
236
|
+
end
|
237
|
+
|
238
|
+
def server_certopts_validate!(certopts)
|
239
|
+
raise "not implemented yet"
|
240
|
+
end
|
241
|
+
|
242
|
+
def server_socket_manager_client
|
243
|
+
socket_manager_path = ENV['SERVERENGINE_SOCKETMANAGER_PATH']
|
244
|
+
if Fluent.windows?
|
245
|
+
socket_manager_path = socket_manager_path.to_i
|
246
|
+
end
|
247
|
+
ServerEngine::SocketManager::Client.new(socket_manager_path)
|
248
|
+
end
|
249
|
+
|
250
|
+
def server_create_tcp_socket(shared, bind, port)
|
251
|
+
sock = if shared
|
252
|
+
server_socket_manager_client.listen_tcp(bind, port)
|
253
|
+
else
|
254
|
+
TCPServer.new(bind, port) # this method call can create sockets for AF_INET6
|
255
|
+
end
|
256
|
+
# close-on-exec is set by default in Ruby 2.0 or later (, and it's unavailable on Windows)
|
257
|
+
sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK) # nonblock
|
258
|
+
sock
|
259
|
+
end
|
260
|
+
|
261
|
+
def server_create_udp_socket(shared, bind, port)
|
262
|
+
sock = if shared
|
263
|
+
server_socket_manager_client.listen_udp(bind, port)
|
264
|
+
else
|
265
|
+
family = IPAddr.new(IPSocket.getaddress(bind)).ipv4? ? ::Socket::AF_INET : ::Socket::AF_INET6
|
266
|
+
usock = UDPSocket.new(family)
|
267
|
+
usock.bind(bind, port)
|
268
|
+
usock
|
269
|
+
end
|
270
|
+
# close-on-exec is set by default in Ruby 2.0 or later (, and it's unavailable on Windows)
|
271
|
+
sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK) # nonblock
|
272
|
+
sock
|
273
|
+
end
|
274
|
+
|
275
|
+
def server_create_tls_socket(shared, bind, port)
|
276
|
+
raise "not implemented yet"
|
277
|
+
end
|
278
|
+
|
279
|
+
class CallbackSocket
|
280
|
+
def initialize(server_type, sock, enabled_events = [], close_socket: true)
|
281
|
+
@server_type = server_type
|
282
|
+
@sock = sock
|
283
|
+
@enabled_events = enabled_events
|
284
|
+
@close_socket = close_socket
|
285
|
+
end
|
286
|
+
|
287
|
+
def remote_addr
|
288
|
+
@sock.peeraddr[3]
|
289
|
+
end
|
290
|
+
|
291
|
+
def remote_host
|
292
|
+
@sock.peeraddr[2]
|
293
|
+
end
|
294
|
+
|
295
|
+
def remote_port
|
296
|
+
@sock.peeraddr[1]
|
297
|
+
end
|
298
|
+
|
299
|
+
def send(data, flags = 0)
|
300
|
+
@sock.send(data, flags)
|
301
|
+
end
|
302
|
+
|
303
|
+
def write(data)
|
304
|
+
raise "not implemented here"
|
305
|
+
end
|
306
|
+
|
307
|
+
def close
|
308
|
+
@sock.close if @close_socket
|
309
|
+
end
|
310
|
+
|
311
|
+
def data(&callback)
|
312
|
+
on(:data, &callback)
|
313
|
+
end
|
314
|
+
|
315
|
+
def on(event, &callback)
|
316
|
+
raise "BUG: this event is disabled for #{@server_type}: #{event}" unless @enabled_events.include?(event)
|
317
|
+
case event
|
318
|
+
when :data
|
319
|
+
@sock.data(&callback)
|
320
|
+
when :write_complete
|
321
|
+
cb = ->(){ callback.call(self) }
|
322
|
+
@sock.on_write_complete(&cb)
|
323
|
+
when :close
|
324
|
+
cb = ->(){ callback.call(self) }
|
325
|
+
@sock.on_close(&cb)
|
326
|
+
else
|
327
|
+
raise "BUG: unknown event: #{event}"
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
class TCPCallbackSocket < CallbackSocket
|
333
|
+
def initialize(sock)
|
334
|
+
super("tcp", sock, [:data, :write_complete, :close])
|
335
|
+
end
|
336
|
+
|
337
|
+
def write(data)
|
338
|
+
@sock.write(data)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class UDPCallbackSocket < CallbackSocket
|
343
|
+
def initialize(sock, peeraddr, **kwargs)
|
344
|
+
super("udp", sock, [], **kwargs)
|
345
|
+
@peeraddr = peeraddr
|
346
|
+
end
|
347
|
+
|
348
|
+
def remote_addr
|
349
|
+
@peeraddr[3]
|
350
|
+
end
|
351
|
+
|
352
|
+
def remote_host
|
353
|
+
@peeraddr[2]
|
354
|
+
end
|
355
|
+
|
356
|
+
def remote_port
|
357
|
+
@peeraddr[1]
|
358
|
+
end
|
359
|
+
|
360
|
+
def write(data)
|
361
|
+
@sock.send(data, 0, @peeraddr[3], @peeraddr[1])
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
module EventHandler
|
366
|
+
class UDPServer < Coolio::IO
|
367
|
+
def initialize(sock, max_bytes, flags, close_socket, log, under_plugin_development, &callback)
|
368
|
+
raise ArgumentError, "socket must be a UDPSocket: sock = #{sock}" unless sock.is_a?(UDPSocket)
|
369
|
+
|
370
|
+
super(sock)
|
371
|
+
|
372
|
+
@sock = sock
|
373
|
+
@max_bytes = max_bytes
|
374
|
+
@flags = flags
|
375
|
+
@close_socket = close_socket
|
376
|
+
@log = log
|
377
|
+
@under_plugin_development = under_plugin_development
|
378
|
+
@callback = callback
|
379
|
+
|
380
|
+
on_readable_impl = case @callback.arity
|
381
|
+
when 1 then :on_readable_without_sock
|
382
|
+
when 2 then :on_readable_with_sock
|
383
|
+
else
|
384
|
+
raise "BUG: callback block must have 1 or 2 arguments"
|
385
|
+
end
|
386
|
+
self.define_singleton_method(:on_readable, method(on_readable_impl))
|
387
|
+
end
|
388
|
+
|
389
|
+
def on_readable_without_sock
|
390
|
+
begin
|
391
|
+
data = @sock.recv(@max_bytes, @flags)
|
392
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNRESET
|
393
|
+
return
|
394
|
+
end
|
395
|
+
@callback.call(data)
|
396
|
+
rescue => e
|
397
|
+
@log.error "unexpected error in processing UDP data", error: e
|
398
|
+
@log.error_backtrace
|
399
|
+
raise if @under_plugin_development
|
400
|
+
end
|
401
|
+
|
402
|
+
def on_readable_with_sock
|
403
|
+
begin
|
404
|
+
data, addr = @sock.recvfrom(@max_bytes)
|
405
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNRESET
|
406
|
+
return
|
407
|
+
end
|
408
|
+
@callback.call(data, UDPCallbackSocket.new(@sock, addr, close_socket: @close_socket))
|
409
|
+
rescue => e
|
410
|
+
@log.error "unexpected error in processing UDP data", error: e
|
411
|
+
@log.error_backtrace
|
412
|
+
raise if @under_plugin_development
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
class TCPServer < Coolio::TCPSocket
|
417
|
+
def initialize(sock, socket_option_setter, close_callback, log, under_plugin_development, connect_callback)
|
418
|
+
raise ArgumentError, "socket must be a TCPSocket: sock=#{sock}" unless sock.is_a?(TCPSocket)
|
419
|
+
|
420
|
+
socket_option_setter.call(sock)
|
421
|
+
|
422
|
+
@_handler_socket = sock
|
423
|
+
super(sock)
|
424
|
+
|
425
|
+
@log = log
|
426
|
+
@under_plugin_development = under_plugin_development
|
427
|
+
|
428
|
+
@connect_callback = connect_callback
|
429
|
+
@data_callback = nil
|
430
|
+
@close_callback = close_callback
|
431
|
+
|
432
|
+
@callback_connection = nil
|
433
|
+
@closing = false
|
434
|
+
|
435
|
+
@mutex = Mutex.new # to serialize #write and #close
|
436
|
+
end
|
437
|
+
|
438
|
+
def data(&callback)
|
439
|
+
raise "data callback can be registered just once, but registered twice" if self.singleton_methods.include?(:on_read)
|
440
|
+
@data_callback = callback
|
441
|
+
on_read_impl = case callback.arity
|
442
|
+
when 1 then :on_read_without_connection
|
443
|
+
when 2 then :on_read_with_connection
|
444
|
+
else
|
445
|
+
raise "BUG: callback block must have 1 or 2 arguments"
|
446
|
+
end
|
447
|
+
self.define_singleton_method(:on_read, method(on_read_impl))
|
448
|
+
end
|
449
|
+
|
450
|
+
def write(data)
|
451
|
+
@mutex.synchronize do
|
452
|
+
super
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def on_connect
|
457
|
+
@callback_connection = TCPCallbackSocket.new(self)
|
458
|
+
@connect_callback.call(@callback_connection)
|
459
|
+
unless @data_callback
|
460
|
+
raise "connection callback must call #data to set data callback"
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def on_read_without_connection(data)
|
465
|
+
@data_callback.call(data)
|
466
|
+
rescue => e
|
467
|
+
@log.error "unexpected error on reading data", host: remote_host, port: remote_port, error: e
|
468
|
+
@log.error_backtrace
|
469
|
+
close(true) rescue nil
|
470
|
+
raise if @under_plugin_development
|
471
|
+
end
|
472
|
+
|
473
|
+
def on_read_with_connection(data)
|
474
|
+
@data_callback.call(data, @callback_connection)
|
475
|
+
rescue => e
|
476
|
+
@log.error "unexpected error on reading data", host: remote_host, port: remote_port, error: e
|
477
|
+
@log.error_backtrace
|
478
|
+
close(true) rescue nil
|
479
|
+
raise if @under_plugin_development
|
480
|
+
end
|
481
|
+
|
482
|
+
def close
|
483
|
+
@mutex.synchronize do
|
484
|
+
return if @closing
|
485
|
+
@closing = true
|
486
|
+
@close_callback.call(self)
|
487
|
+
super
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|