fluentd 1.12.1 → 1.12.2

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.

Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.deepsource.toml +13 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +2 -2
  4. data/.github/workflows/linux-test.yaml +36 -0
  5. data/.github/workflows/macos-test.yaml +30 -0
  6. data/.github/workflows/{build.yaml → windows-test.yaml} +7 -6
  7. data/CHANGELOG.md +47 -0
  8. data/MAINTAINERS.md +5 -2
  9. data/fluentd.gemspec +1 -0
  10. data/lib/fluent/command/bundler_injection.rb +1 -1
  11. data/lib/fluent/command/cat.rb +0 -1
  12. data/lib/fluent/command/plugin_config_formatter.rb +2 -1
  13. data/lib/fluent/compat/parser.rb +2 -2
  14. data/lib/fluent/config/section.rb +1 -1
  15. data/lib/fluent/config/types.rb +2 -2
  16. data/lib/fluent/event.rb +3 -13
  17. data/lib/fluent/load.rb +0 -1
  18. data/lib/fluent/plugin/formatter_ltsv.rb +2 -2
  19. data/lib/fluent/plugin/in_http.rb +1 -1
  20. data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
  21. data/lib/fluent/plugin/in_tail.rb +34 -15
  22. data/lib/fluent/plugin/out_copy.rb +18 -5
  23. data/lib/fluent/plugin/out_exec_filter.rb +3 -3
  24. data/lib/fluent/plugin/out_forward.rb +61 -28
  25. data/lib/fluent/plugin/storage_local.rb +3 -3
  26. data/lib/fluent/supervisor.rb +1 -1
  27. data/lib/fluent/time.rb +57 -1
  28. data/lib/fluent/version.rb +1 -1
  29. data/test/command/test_fluentd.rb +8 -0
  30. data/test/config/test_configurable.rb +1 -1
  31. data/test/plugin/in_tail/test_position_file.rb +4 -4
  32. data/test/plugin/out_forward/test_connection_manager.rb +6 -0
  33. data/test/plugin/test_in_exec.rb +1 -1
  34. data/test/plugin/test_in_tail.rb +54 -16
  35. data/test/plugin/test_out_copy.rb +87 -0
  36. data/test/plugin/test_out_forward.rb +74 -0
  37. data/test/plugin/test_out_http.rb +1 -1
  38. data/test/plugin_helper/test_child_process.rb +5 -2
  39. data/test/test_event.rb +16 -0
  40. data/test/test_formatter.rb +30 -0
  41. data/test/test_time_parser.rb +109 -0
  42. metadata +27 -7
  43. data/.travis.yml +0 -77
  44. data/appveyor.yml +0 -31
@@ -159,9 +159,9 @@ module Fluent::Plugin
159
159
  @added_prefix_string = @add_prefix + '.'
160
160
  end
161
161
 
162
- @respawns = if @child_respawn.nil? or @child_respawn == 'none' or @child_respawn == '0'
162
+ @respawns = if @child_respawn.nil? || (@child_respawn == 'none') || (@child_respawn == '0')
163
163
  0
164
- elsif @child_respawn == 'inf' or @child_respawn == '-1'
164
+ elsif (@child_respawn == 'inf') || (@child_respawn == '-1')
165
165
  -1
166
166
  elsif @child_respawn =~ /^\d+$/
167
167
  @child_respawn.to_i
@@ -251,7 +251,7 @@ module Fluent::Plugin
251
251
 
252
252
  def tag_remove_prefix(tag)
253
253
  if @remove_prefix
254
- if (tag[0, @removed_length] == @removed_prefix_string and tag.length > @removed_length) or tag == @removed_prefix_string
254
+ if ((tag[0, @removed_length] == @removed_prefix_string) && (tag.length > @removed_length)) || (tag == @removed_prefix_string)
255
255
  tag = tag[@removed_length..-1] || ''
256
256
  end
257
257
  end
@@ -166,6 +166,7 @@ module Fluent::Plugin
166
166
 
167
167
  @usock = nil
168
168
  @keep_alive_watcher_interval = 5 # TODO
169
+ @suspend_flush = false
169
170
  end
170
171
 
171
172
  def configure(conf)
@@ -291,6 +292,15 @@ module Fluent::Plugin
291
292
  @require_ack_response
292
293
  end
293
294
 
295
+ def overwrite_delayed_commit_timeout
296
+ # Output#start sets @delayed_commit_timeout by @buffer_config.delayed_commit_timeout
297
+ # But it should be overwritten by ack_response_timeout to rollback chunks after timeout
298
+ if @delayed_commit_timeout != @ack_response_timeout
299
+ log.info "delayed_commit_timeout is overwritten by ack_response_timeout"
300
+ @delayed_commit_timeout = @ack_response_timeout + 2 # minimum ack_reader IO.select interval is 1s
301
+ end
302
+ end
303
+
294
304
  def start
295
305
  super
296
306
 
@@ -303,13 +313,7 @@ module Fluent::Plugin
303
313
  end
304
314
 
305
315
  if @require_ack_response
306
- # Output#start sets @delayed_commit_timeout by @buffer_config.delayed_commit_timeout
307
- # But it should be overwritten by ack_response_timeout to rollback chunks after timeout
308
- if @delayed_commit_timeout != @ack_response_timeout
309
- log.info "delayed_commit_timeout is overwritten by ack_response_timeout"
310
- @delayed_commit_timeout = @ack_response_timeout + 2 # minimum ack_reader IO.select interval is 1s
311
- end
312
-
316
+ overwrite_delayed_commit_timeout
313
317
  thread_create(:out_forward_receiving_ack, &method(:ack_reader))
314
318
  end
315
319
 
@@ -346,6 +350,26 @@ module Fluent::Plugin
346
350
  end
347
351
  end
348
352
 
353
+ def before_shutdown
354
+ super
355
+ @suspend_flush = true
356
+ end
357
+
358
+ def after_shutdown
359
+ last_ack if @require_ack_response
360
+ super
361
+ end
362
+
363
+ def try_flush
364
+ return if @require_ack_response && @suspend_flush
365
+ super
366
+ end
367
+
368
+ def last_ack
369
+ overwrite_delayed_commit_timeout
370
+ ack_check(ack_select_interval)
371
+ end
372
+
349
373
  def write(chunk)
350
374
  return if chunk.empty?
351
375
  tag = chunk.metadata.tag
@@ -361,6 +385,7 @@ module Fluent::Plugin
361
385
  end
362
386
  tag = chunk.metadata.tag
363
387
  discovery_manager.select_service { |node| node.send_data(tag, chunk) }
388
+ last_ack if @require_ack_response && @suspend_flush
364
389
  end
365
390
 
366
391
  def create_transfer_socket(host, port, hostname, &block)
@@ -481,31 +506,39 @@ module Fluent::Plugin
481
506
  @connection_manager.purge_obsolete_socks
482
507
  end
483
508
 
509
+ def ack_select_interval
510
+ if @delayed_commit_timeout > 3
511
+ 1
512
+ else
513
+ @delayed_commit_timeout / 3.0
514
+ end
515
+ end
516
+
484
517
  def ack_reader
485
- select_interval = if @delayed_commit_timeout > 3
486
- 1
487
- else
488
- @delayed_commit_timeout / 3.0
489
- end
518
+ select_interval = ack_select_interval
490
519
 
491
520
  while thread_current_running?
492
- @ack_handler.collect_response(select_interval) do |chunk_id, node, sock, result|
493
- @connection_manager.close(sock)
494
-
495
- case result
496
- when AckHandler::Result::SUCCESS
497
- commit_write(chunk_id)
498
- when AckHandler::Result::FAILED
499
- node.disable!
500
- rollback_write(chunk_id, update_retry: false)
501
- when AckHandler::Result::CHUNKID_UNMATCHED
502
- rollback_write(chunk_id, update_retry: false)
503
- else
504
- log.warn("BUG: invalid status #{result} #{chunk_id}")
521
+ ack_check(select_interval)
522
+ end
523
+ end
505
524
 
506
- if chunk_id
507
- rollback_write(chunk_id, update_retry: false)
508
- end
525
+ def ack_check(select_interval)
526
+ @ack_handler.collect_response(select_interval) do |chunk_id, node, sock, result|
527
+ @connection_manager.close(sock)
528
+
529
+ case result
530
+ when AckHandler::Result::SUCCESS
531
+ commit_write(chunk_id)
532
+ when AckHandler::Result::FAILED
533
+ node.disable!
534
+ rollback_write(chunk_id, update_retry: false)
535
+ when AckHandler::Result::CHUNKID_UNMATCHED
536
+ rollback_write(chunk_id, update_retry: false)
537
+ else
538
+ log.warn("BUG: invalid status #{result} #{chunk_id}")
539
+
540
+ if chunk_id
541
+ rollback_write(chunk_id, update_retry: false)
509
542
  end
510
543
  end
511
544
  end
@@ -87,7 +87,7 @@ module Fluent
87
87
  if File.exist?(@path)
88
88
  raise Fluent::ConfigError, "Plugin storage path '#{@path}' is not readable/writable" unless File.readable?(@path) && File.writable?(@path)
89
89
  begin
90
- data = open(@path, 'r:utf-8') { |io| io.read }
90
+ data = File.open(@path, 'r:utf-8') { |io| io.read }
91
91
  if data.empty?
92
92
  log.warn "detect empty plugin storage file during startup. Ignored: #{@path}"
93
93
  return
@@ -115,7 +115,7 @@ module Fluent
115
115
  return if @on_memory
116
116
  return unless File.exist?(@path)
117
117
  begin
118
- json_string = open(@path, 'r:utf-8'){ |io| io.read }
118
+ json_string = File.open(@path, 'r:utf-8'){ |io| io.read }
119
119
  json = Yajl::Parser.parse(json_string)
120
120
  unless json.is_a?(Hash)
121
121
  log.error "broken content for plugin storage (Hash required: ignored)", type: json.class
@@ -133,7 +133,7 @@ module Fluent
133
133
  tmp_path = @path + '.tmp'
134
134
  begin
135
135
  json_string = Yajl::Encoder.encode(@store, pretty: @pretty_print)
136
- open(tmp_path, 'w:utf-8', @mode) { |io| io.write json_string; io.fsync }
136
+ File.open(tmp_path, 'w:utf-8', @mode) { |io| io.write json_string; io.fsync }
137
137
  File.rename(tmp_path, @path)
138
138
  rescue => e
139
139
  log.error "failed to save data for plugin storage to file", path: @path, tmp: tmp_path, error: e
@@ -385,7 +385,7 @@ module Fluent
385
385
  config_mtime = File.mtime(path)
386
386
 
387
387
  # reuse previous config if last load time is within 5 seconds and mtime of the config file is not changed
388
- if Time.now - Time.at(pre_loadtime) < 5 and config_mtime == pre_config_mtime
388
+ if (Time.now - Time.at(pre_loadtime) < 5) && (config_mtime == pre_config_mtime)
389
389
  return params['pre_conf']
390
390
  end
391
391
 
data/lib/fluent/time.rb CHANGED
@@ -132,13 +132,14 @@ module Fluent
132
132
  end
133
133
 
134
134
  module TimeMixin
135
- TIME_TYPES = ['string', 'unixtime', 'float']
135
+ TIME_TYPES = ['string', 'unixtime', 'float', 'mixed']
136
136
 
137
137
  TIME_PARAMETERS = [
138
138
  [:time_format, :string, {default: nil}],
139
139
  [:localtime, :bool, {default: true}], # UTC if :localtime is false and :timezone is nil
140
140
  [:utc, :bool, {default: false}], # to turn :localtime false
141
141
  [:timezone, :string, {default: nil}],
142
+ [:time_format_fallbacks, :array, {default: []}], # try time_format, then try fallbacks
142
143
  ]
143
144
  TIME_FULL_PARAMETERS = [
144
145
  # To avoid to define :time_type twice (in plugin_helper/inject)
@@ -170,6 +171,12 @@ module Fluent
170
171
  raise Fluent::ConfigError, "both of utc and localtime are specified, use only one of them"
171
172
  end
172
173
 
174
+ if conf.has_key?('time_type') and @time_type == :mixed
175
+ if @time_format.nil? and @time_format_fallbacks.empty?
176
+ raise Fluent::ConfigError, "time_type is :mixed but time_format and time_format_fallbacks is empty."
177
+ end
178
+ end
179
+
173
180
  Fluent::Timezone.validate!(@timezone) if @timezone
174
181
  end
175
182
  end
@@ -180,6 +187,7 @@ module Fluent
180
187
  end
181
188
 
182
189
  def time_parser_create(type: @time_type, format: @time_format, timezone: @timezone, force_localtime: false)
190
+ return MixedTimeParser.new(type, format, @localtime, timezone, @utc, force_localtime, @time_format_fallbacks) if type == :mixed
183
191
  return NumericTimeParser.new(type) if type != :string
184
192
  return TimeParser.new(format, true, nil) if force_localtime
185
193
 
@@ -452,4 +460,52 @@ module Fluent
452
460
  end
453
461
  end
454
462
  end
463
+
464
+ # MixedTimeParser is available when time_type is set to :mixed
465
+ #
466
+ # Use Case 1: primary format is specified explicitly in time_format
467
+ # time_type mixed
468
+ # time_format %iso8601
469
+ # time_format_fallbacks unixtime
470
+ # Use Case 2: time_format is omitted
471
+ # time_type mixed
472
+ # time_format_fallbacks %iso8601, unixtime
473
+ #
474
+ class MixedTimeParser < TimeParser # to include TimeParseError
475
+ def initialize(type, format = nil, localtime = nil, timezone = nil, utc = nil, force_localtime = nil, fallbacks = [])
476
+ @parsers = []
477
+ fallbacks.unshift(format).each do |fallback|
478
+ next unless fallback
479
+ case fallback
480
+ when 'unixtime', 'float'
481
+ @parsers << NumericTimeParser.new(fallback, localtime, timezone)
482
+ else
483
+ if force_localtime
484
+ @parsers << TimeParser.new(fallback, true, nil)
485
+ else
486
+ localtime = localtime && (timezone.nil? && !utc)
487
+ @parsers << TimeParser.new(fallback, localtime, timezone)
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ def parse(value)
494
+ @parsers.each do |parser|
495
+ begin
496
+ Float(value) if parser.class == Fluent::NumericTimeParser
497
+ rescue
498
+ next
499
+ end
500
+ begin
501
+ return parser.parse(value)
502
+ rescue
503
+ # skip TimeParseError
504
+ end
505
+ end
506
+ fallback_class = @parsers.collect do |parser| parser.class end.join(",")
507
+ raise TimeParseError, "invalid time format: value = #{value}, even though fallbacks: #{fallback_class}"
508
+ end
509
+ end
510
+
455
511
  end
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.12.1'
19
+ VERSION = '1.12.2'
20
20
 
21
21
  end
@@ -878,6 +878,8 @@ CONF
878
878
  end
879
879
 
880
880
  test "without RUBYOPT" do
881
+ saved_ruby_opt = ENV["RUBYOPT"]
882
+ ENV["RUBYOPT"] = nil
881
883
  conf = <<CONF
882
884
  <source>
883
885
  @type dummy
@@ -889,6 +891,8 @@ CONF
889
891
  CONF
890
892
  conf_path = create_conf_file('rubyopt_test.conf', conf)
891
893
  assert_log_matches(create_cmdline(conf_path), '-Eascii-8bit:ascii-8bit')
894
+ ensure
895
+ ENV["RUBYOPT"] = saved_ruby_opt
892
896
  end
893
897
 
894
898
  test 'invalid values are set to RUBYOPT' do
@@ -912,6 +916,8 @@ CONF
912
916
 
913
917
  # https://github.com/fluent/fluentd/issues/2915
914
918
  test "ruby path contains spaces" do
919
+ saved_ruby_opt = ENV["RUBYOPT"]
920
+ ENV["RUBYOPT"] = nil
915
921
  conf = <<CONF
916
922
  <source>
917
923
  @type dummy
@@ -940,6 +946,8 @@ CONF
940
946
  'spawn command to main:',
941
947
  '-Eascii-8bit:ascii-8bit'
942
948
  )
949
+ ensure
950
+ ENV["RUBYOPT"] = saved_ruby_opt
943
951
  end
944
952
 
945
953
  test 'success to start workers when file buffer is configured in non-workers way only for specific worker' do
@@ -1453,7 +1453,7 @@ module Fluent::Config
1453
1453
  @example = ConfigurableSpec::ExampleWithSkipAccessor.new
1454
1454
  @example.configure(config_element('ROOT'))
1455
1455
  assert_equal 'example7', @example.instance_variable_get(:@name)
1456
- assert_raise NoMethodError.new("undefined method `name' for #{@example}") do
1456
+ assert_raise NoMethodError do
1457
1457
  @example.name
1458
1458
  end
1459
1459
  end
@@ -147,7 +147,7 @@ class IntailPositionFileTest < Test::Unit::TestCase
147
147
  write_data(@file, TEST_CONTENT)
148
148
  pf = Fluent::Plugin::TailInput::PositionFile.load(@file, false, {}, **{logger: $log})
149
149
 
150
- valid_target_info = Fluent::Plugin::TailInput::TargetInfo.new('valid_path', Fluent::FileWrapper.stat(@file).ino)
150
+ valid_target_info = Fluent::Plugin::TailInput::TargetInfo.new('valid_path', File.stat(@file).ino)
151
151
  f = pf[valid_target_info]
152
152
  assert_equal Fluent::Plugin::TailInput::FilePositionEntry, f.class
153
153
  assert_equal 2, f.read_pos
@@ -177,7 +177,7 @@ class IntailPositionFileTest < Test::Unit::TestCase
177
177
  assert_equal 0, f.read_inode
178
178
  assert_equal 0, f.read_pos
179
179
 
180
- pf[Fluent::Plugin::TailInput::TargetInfo.new('valid_path', Fluent::FileWrapper.stat(@file).ino)].update(1, 2)
180
+ pf[Fluent::Plugin::TailInput::TargetInfo.new('valid_path', File.stat(@file).ino)].update(1, 2)
181
181
 
182
182
  f = pf[Fluent::Plugin::TailInput::TargetInfo.new('nonexist_path', -1)]
183
183
  assert_equal 0, f.read_inode
@@ -193,7 +193,7 @@ class IntailPositionFileTest < Test::Unit::TestCase
193
193
  test 'deletes entry by path' do
194
194
  write_data(@file, TEST_CONTENT)
195
195
  pf = Fluent::Plugin::TailInput::PositionFile.load(@file, false, {}, logger: $log)
196
- inode1 = Fluent::FileWrapper.stat(@file).ino
196
+ inode1 = File.stat(@file).ino
197
197
  target_info1 = Fluent::Plugin::TailInput::TargetInfo.new('valid_path', inode1)
198
198
  p1 = pf[target_info1]
199
199
  assert_equal Fluent::Plugin::TailInput::FilePositionEntry, p1.class
@@ -201,7 +201,7 @@ class IntailPositionFileTest < Test::Unit::TestCase
201
201
  pf.unwatch(target_info1)
202
202
  assert_equal p1.read_pos, Fluent::Plugin::TailInput::PositionFile::UNWATCHED_POSITION
203
203
 
204
- inode2 = Fluent::FileWrapper.stat(@file).ino
204
+ inode2 = File.stat(@file).ino
205
205
  target_info2 = Fluent::Plugin::TailInput::TargetInfo.new('valid_path', inode2)
206
206
  p2 = pf[target_info2]
207
207
  assert_equal Fluent::Plugin::TailInput::FilePositionEntry, p2.class
@@ -81,6 +81,8 @@ class ConnectionManager < Test::Unit::TestCase
81
81
 
82
82
  sub_test_case 'when socket_cache exists' do
83
83
  test 'calls connect_keepalive' do
84
+ omit "Proxy of RR doesn't support kwargs of Ruby 3 yet" if RUBY_VERSION.split('.')[0].to_i >= 3
85
+
84
86
  cache = Fluent::Plugin::ForwardOutput::SocketCache.new(10, $log)
85
87
  mock(cache).checkin('sock').never
86
88
 
@@ -99,6 +101,8 @@ class ConnectionManager < Test::Unit::TestCase
99
101
  end
100
102
 
101
103
  test 'calls connect_keepalive and closes socket with block' do
104
+ omit "Proxy of RR doesn't support kwargs of Ruby 3 yet" if RUBY_VERSION.split('.')[0].to_i >= 3
105
+
102
106
  cache = Fluent::Plugin::ForwardOutput::SocketCache.new(10, $log)
103
107
  mock(cache).checkin('sock').once
104
108
 
@@ -118,6 +122,8 @@ class ConnectionManager < Test::Unit::TestCase
118
122
  end
119
123
 
120
124
  test 'does not call dec_ref when ack is passed' do
125
+ omit "Proxy of RR doesn't support kwargs of Ruby 3 yet" if RUBY_VERSION.split('.')[0].to_i >= 3
126
+
121
127
  cache = Fluent::Plugin::ForwardOutput::SocketCache.new(10, $log)
122
128
  mock(cache).checkin('sock').never
123
129
  sock = 'sock'
@@ -255,7 +255,7 @@ EOC
255
255
  assert{ d.events.length > 0 }
256
256
  d.events.each do |event|
257
257
  assert_equal 'test', event[0]
258
- assert_match /LoadError/, event[2]['message']
258
+ assert_match(/LoadError/, event[2]['message'])
259
259
  end
260
260
  end
261
261
  end
@@ -6,6 +6,8 @@ require 'fluent/system_config'
6
6
  require 'net/http'
7
7
  require 'flexmock/test_unit'
8
8
  require 'timecop'
9
+ require 'tmpdir'
10
+ require 'securerandom'
9
11
 
10
12
  class TailInputTest < Test::Unit::TestCase
11
13
  include FlexMock::TestCase
@@ -22,28 +24,64 @@ class TailInputTest < Test::Unit::TestCase
22
24
  end
23
25
 
24
26
  def cleanup_directory(path)
25
- begin
26
- FileUtils.rm_f(path, secure: true)
27
- rescue ArgumentError
28
- FileUtils.rm_f(path) # For Ruby 2.6 or before.
27
+ unless Dir.exist?(path)
28
+ FileUtils.mkdir_p(path)
29
+ return
29
30
  end
30
- if File.exist?(path)
31
- FileUtils.remove_entry_secure(path, true)
31
+
32
+ if Fluent.windows?
33
+ Dir.glob("*", base: path).each do |name|
34
+ begin
35
+ cleanup_file(File.join(path, name))
36
+ rescue
37
+ # expect test driver block release already owned file handle.
38
+ end
39
+ end
40
+ else
41
+ begin
42
+ FileUtils.rm_f(path, secure:true)
43
+ rescue ArgumentError
44
+ FileUtils.rm_f(path) # For Ruby 2.6 or before.
45
+ end
46
+ if File.exist?(path)
47
+ FileUtils.remove_entry_secure(path, true)
48
+ end
32
49
  end
33
50
  FileUtils.mkdir_p(path)
34
51
  end
35
52
 
36
53
  def cleanup_file(path)
37
- begin
38
- FileUtils.rm_f(path, secure: true)
39
- rescue ArgumentError
40
- FileUtils.rm_f(path) # For Ruby 2.6 or before.
41
- end
42
- if File.exist?(path)
43
- # ensure files are closed for Windows, on which deleted files
44
- # are still visible from filesystem
45
- GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
46
- FileUtils.remove_entry_secure(path, true)
54
+ if Fluent.windows?
55
+ # On Windows, when the file or directory is removed and created
56
+ # frequently, there is a case that creating file or directory will
57
+ # fail. This situation is caused by pending file or directory
58
+ # deletion which is mentioned on win32 API document [1]
59
+ # As a workaround, execute rename and remove method.
60
+ #
61
+ # [1] https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#files
62
+ #
63
+ file = File.join(Dir.tmpdir, SecureRandom.hex(10))
64
+ begin
65
+ FileUtils.mv(path, file)
66
+ FileUtils.rm_rf(file, secure: true)
67
+ rescue ArgumentError
68
+ FileUtils.rm_rf(file) # For Ruby 2.6 or before.
69
+ end
70
+ if File.exist?(file)
71
+ # ensure files are closed for Windows, on which deleted files
72
+ # are still visible from filesystem
73
+ GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
74
+ FileUtils.remove_entry_secure(file, true)
75
+ end
76
+ else
77
+ begin
78
+ FileUtils.rm_f(path, secure: true)
79
+ rescue ArgumentError
80
+ FileUtils.rm_f(path) # For Ruby 2.6 or before.
81
+ end
82
+ if File.exist?(path)
83
+ FileUtils.remove_entry_secure(path, true)
84
+ end
47
85
  end
48
86
  end
49
87