sidekiq 6.3.1 → 7.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +285 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +47 -34
  5. data/bin/sidekiq +4 -9
  6. data/bin/sidekiqload +207 -117
  7. data/bin/sidekiqmon +4 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +333 -190
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +86 -80
  15. data/lib/sidekiq/client.rb +104 -95
  16. data/lib/sidekiq/{util.rb → component.rb} +14 -41
  17. data/lib/sidekiq/config.rb +282 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +23 -24
  21. data/lib/sidekiq/job.rb +371 -10
  22. data/lib/sidekiq/job_logger.rb +16 -28
  23. data/lib/sidekiq/job_retry.rb +99 -58
  24. data/lib/sidekiq/job_util.rb +107 -0
  25. data/lib/sidekiq/launcher.rb +103 -95
  26. data/lib/sidekiq/logger.rb +9 -44
  27. data/lib/sidekiq/manager.rb +40 -41
  28. data/lib/sidekiq/metrics/query.rb +153 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +136 -0
  31. data/lib/sidekiq/middleware/chain.rb +96 -51
  32. data/lib/sidekiq/middleware/current_attributes.rb +59 -16
  33. data/lib/sidekiq/middleware/i18n.rb +6 -4
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +17 -4
  36. data/lib/sidekiq/paginator.rb +17 -9
  37. data/lib/sidekiq/processor.rb +81 -80
  38. data/lib/sidekiq/rails.rb +21 -14
  39. data/lib/sidekiq/redis_client_adapter.rb +95 -0
  40. data/lib/sidekiq/redis_connection.rb +14 -82
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +76 -38
  43. data/lib/sidekiq/testing/inline.rb +4 -4
  44. data/lib/sidekiq/testing.rb +42 -69
  45. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  46. data/lib/sidekiq/version.rb +2 -1
  47. data/lib/sidekiq/web/action.rb +3 -3
  48. data/lib/sidekiq/web/application.rb +95 -11
  49. data/lib/sidekiq/web/csrf_protection.rb +4 -4
  50. data/lib/sidekiq/web/helpers.rb +58 -30
  51. data/lib/sidekiq/web.rb +22 -17
  52. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  53. data/lib/sidekiq.rb +85 -202
  54. data/sidekiq.gemspec +12 -10
  55. data/web/assets/javascripts/application.js +77 -26
  56. data/web/assets/javascripts/base-charts.js +106 -0
  57. data/web/assets/javascripts/chart.min.js +13 -0
  58. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  59. data/web/assets/javascripts/dashboard-charts.js +168 -0
  60. data/web/assets/javascripts/dashboard.js +3 -240
  61. data/web/assets/javascripts/metrics.js +264 -0
  62. data/web/assets/stylesheets/application-dark.css +17 -17
  63. data/web/assets/stylesheets/application-rtl.css +2 -91
  64. data/web/assets/stylesheets/application.css +69 -302
  65. data/web/locales/ar.yml +70 -70
  66. data/web/locales/cs.yml +62 -62
  67. data/web/locales/da.yml +60 -53
  68. data/web/locales/de.yml +65 -65
  69. data/web/locales/el.yml +43 -24
  70. data/web/locales/en.yml +84 -69
  71. data/web/locales/es.yml +68 -68
  72. data/web/locales/fa.yml +65 -65
  73. data/web/locales/fr.yml +81 -67
  74. data/web/locales/gd.yml +99 -0
  75. data/web/locales/he.yml +65 -64
  76. data/web/locales/hi.yml +59 -59
  77. data/web/locales/it.yml +53 -53
  78. data/web/locales/ja.yml +73 -68
  79. data/web/locales/ko.yml +52 -52
  80. data/web/locales/lt.yml +66 -66
  81. data/web/locales/nb.yml +61 -61
  82. data/web/locales/nl.yml +52 -52
  83. data/web/locales/pl.yml +45 -45
  84. data/web/locales/pt-br.yml +83 -55
  85. data/web/locales/pt.yml +51 -51
  86. data/web/locales/ru.yml +67 -66
  87. data/web/locales/sv.yml +53 -53
  88. data/web/locales/ta.yml +60 -60
  89. data/web/locales/uk.yml +62 -61
  90. data/web/locales/ur.yml +64 -64
  91. data/web/locales/vi.yml +67 -67
  92. data/web/locales/zh-cn.yml +43 -16
  93. data/web/locales/zh-tw.yml +42 -8
  94. data/web/views/_footer.erb +5 -2
  95. data/web/views/_job_info.erb +18 -2
  96. data/web/views/_metrics_period_select.erb +12 -0
  97. data/web/views/_nav.erb +1 -1
  98. data/web/views/_paging.erb +2 -0
  99. data/web/views/_poll_link.erb +1 -1
  100. data/web/views/_summary.erb +1 -1
  101. data/web/views/busy.erb +44 -28
  102. data/web/views/dashboard.erb +36 -4
  103. data/web/views/filtering.erb +7 -0
  104. data/web/views/metrics.erb +82 -0
  105. data/web/views/metrics_for_job.erb +68 -0
  106. data/web/views/morgue.erb +5 -9
  107. data/web/views/queue.erb +15 -15
  108. data/web/views/queues.erb +3 -1
  109. data/web/views/retries.erb +5 -9
  110. data/web/views/scheduled.erb +12 -13
  111. metadata +62 -31
  112. data/LICENSE +0 -9
  113. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  114. data/lib/sidekiq/delay.rb +0 -41
  115. data/lib/sidekiq/exception_handler.rb +0 -27
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/worker.rb +0 -311
data/bin/sidekiqload CHANGED
@@ -1,87 +1,71 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ #
4
+ # bin/sidekiqload is a helpful script to load test and
5
+ # performance tune Sidekiq's core. It creates 500,000 no-op
6
+ # jobs and executes them as fast as possible.
7
+ # Example Usage:
8
+ #
9
+ # > RUBY_YJIT_ENABLE=1 LATENCY=0 THREADS=10 bin/sidekiqload
10
+ # Result: Done, 500000 jobs in 20.264945 sec, 24673 jobs/sec
11
+ #
12
+ # Use LATENCY=1 to get a more real world network setup
13
+ # but you'll need to setup and start toxiproxy as noted below.
14
+ #
15
+ # Use AJ=1 to test ActiveJob instead of plain old Sidekiq::Jobs so
16
+ # you can see the runtime performance difference between the two APIs.
17
+ #
18
+ # None of this script is considered a public API and may change over time.
19
+ #
20
+
3
21
  # Quiet some warnings we see when running in warning mode:
4
22
  # RUBYOPT=-w bundle exec sidekiq
5
23
  $TESTING = false
24
+ puts RUBY_DESCRIPTION
25
+ puts(%w[THREADS LATENCY AJ PROFILE].map { |x| "#{x}: #{ENV[x] || "nil"}" }.join(", "))
6
26
 
7
- #require 'ruby-prof'
8
- require 'bundler/setup'
27
+ require "ruby-prof" if ENV["PROFILE"]
28
+ require "bundler/setup"
9
29
  Bundler.require(:default, :load_test)
10
30
 
11
- require_relative '../lib/sidekiq/cli'
12
- require_relative '../lib/sidekiq/launcher'
31
+ latency = Integer(ENV["LATENCY"] || 1)
32
+ if latency > 0
33
+ # brew tap shopify/shopify
34
+ # brew install toxiproxy
35
+ # run `toxiproxy-server` in a separate terminal window.
36
+ require "toxiproxy"
37
+ # simulate a non-localhost network for realer-world conditions.
38
+ # adding 1ms of network latency has an ENORMOUS impact on benchmarks
39
+ Toxiproxy.populate([{
40
+ name: "redis",
41
+ listen: "127.0.0.1:6380",
42
+ upstream: "127.0.0.1:6379"
43
+ }])
44
+ end
13
45
 
14
- include Sidekiq::Util
46
+ if ENV["AJ"]
47
+ require "active_job"
48
+ puts "Using ActiveJob #{ActiveJob::VERSION::STRING}"
49
+ ActiveJob::Base.queue_adapter = :sidekiq
50
+ ActiveJob::Base.logger.level = Logger::WARN
15
51
 
16
- Sidekiq.configure_server do |config|
17
- config.options[:concurrency] = 10
18
- config.redis = { db: 13, port: 6380, driver: :hiredis }
19
- config.options[:queues] << 'default'
20
- config.logger.level = Logger::ERROR
21
- config.average_scheduled_poll_interval = 2
22
- config.reliable! if defined?(Sidekiq::Pro)
52
+ class LoadJob < ActiveJob::Base
53
+ def perform(idx, ts = nil)
54
+ puts(Time.now.to_f - ts) if !ts.nil?
55
+ end
56
+ end
23
57
  end
24
58
 
25
59
  class LoadWorker
26
- include Sidekiq::Worker
60
+ include Sidekiq::Job
27
61
  sidekiq_options retry: 1
28
62
  sidekiq_retry_in do |x|
29
63
  1
30
64
  end
31
65
 
32
- def perform(idx, ts=nil)
33
- puts(Time.now.to_f - ts) if ts != nil
34
- #raise idx.to_s if idx % 100 == 1
35
- end
36
- end
37
-
38
- # brew tap shopify/shopify
39
- # brew install toxiproxy
40
- # gem install toxiproxy
41
- # run `toxiproxy-server` in a separate terminal window.
42
- require 'toxiproxy'
43
- # simulate a non-localhost network for realer-world conditions.
44
- # adding 1ms of network latency has an ENORMOUS impact on benchmarks
45
- Toxiproxy.populate([{
46
- "name": "redis",
47
- "listen": "127.0.0.1:6380",
48
- "upstream": "127.0.0.1:6379"
49
- }])
50
-
51
- self_read, self_write = IO.pipe
52
- %w(INT TERM TSTP TTIN).each do |sig|
53
- begin
54
- trap sig do
55
- self_write.puts(sig)
56
- end
57
- rescue ArgumentError
58
- puts "Signal #{sig} not supported"
59
- end
60
- end
61
-
62
- Sidekiq.redis {|c| c.flushdb}
63
- def handle_signal(launcher, sig)
64
- Sidekiq.logger.debug "Got #{sig} signal"
65
- case sig
66
- when 'INT'
67
- # Handle Ctrl-C in JRuby like MRI
68
- # http://jira.codehaus.org/browse/JRUBY-4637
69
- raise Interrupt
70
- when 'TERM'
71
- # Heroku sends TERM and then waits 30 seconds for process to exit.
72
- raise Interrupt
73
- when 'TSTP'
74
- Sidekiq.logger.info "Received TSTP, no longer accepting new work"
75
- launcher.quiet
76
- when 'TTIN'
77
- Thread.list.each do |thread|
78
- Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['label']}"
79
- if thread.backtrace
80
- Sidekiq.logger.warn thread.backtrace.join("\n")
81
- else
82
- Sidekiq.logger.warn "<no backtrace available>"
83
- end
84
- end
66
+ def perform(idx, ts = nil)
67
+ puts(Time.now.to_f - ts) if !ts.nil?
68
+ # raise idx.to_s if idx % 100 == 1
85
69
  end
86
70
  end
87
71
 
@@ -89,69 +73,175 @@ def Process.rss
89
73
  `ps -o rss= -p #{Process.pid}`.chomp.to_i
90
74
  end
91
75
 
92
- iter = 10
93
- count = 10_000
76
+ class Loader
77
+ def initialize
78
+ @iter = ENV["GC"] ? 10 : 500
79
+ @count = Integer(ENV["COUNT"] || 1_000)
80
+ @latency = Integer(ENV["LATENCY"] || 1)
81
+ end
82
+
83
+ def configure
84
+ @x = Sidekiq.configure_embed do |config|
85
+ config.redis = {db: 13, port: ((@latency > 0) ? 6380 : 6379)}
86
+ config.concurrency = Integer(ENV.fetch("THREADS", "10"))
87
+ # config.redis = { db: 13, port: 6380, driver: :hiredis}
88
+ config.queues = %w[default]
89
+ config.logger.level = Logger::WARN
90
+ config.average_scheduled_poll_interval = 2
91
+ config.reliable! if defined?(Sidekiq::Pro)
92
+ end
94
93
 
95
- iter.times do
96
- arr = Array.new(count) do
97
- []
94
+ @self_read, @self_write = IO.pipe
95
+ %w[INT TERM TSTP TTIN].each do |sig|
96
+ trap sig do
97
+ @self_write.puts(sig)
98
+ end
99
+ rescue ArgumentError
100
+ puts "Signal #{sig} not supported"
101
+ end
98
102
  end
99
- count.times do |idx|
100
- arr[idx][0] = idx
103
+
104
+ def handle_signal(sig)
105
+ launcher = @x
106
+ Sidekiq.logger.debug "Got #{sig} signal"
107
+ case sig
108
+ when "INT"
109
+ # Handle Ctrl-C in JRuby like MRI
110
+ # http://jira.codehaus.org/browse/JRUBY-4637
111
+ raise Interrupt
112
+ when "TERM"
113
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
114
+ raise Interrupt
115
+ when "TSTP"
116
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
117
+ launcher.quiet
118
+ when "TTIN"
119
+ Thread.list.each do |thread|
120
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}"
121
+ if thread.backtrace
122
+ Sidekiq.logger.warn thread.backtrace.join("\n")
123
+ else
124
+ Sidekiq.logger.warn "<no backtrace available>"
125
+ end
126
+ end
127
+ end
101
128
  end
102
- Sidekiq::Client.push_bulk('class' => LoadWorker, 'args' => arr)
103
- end
104
- Sidekiq.logger.error "Created #{count*iter} jobs"
105
129
 
106
- start = Time.now
130
+ def setup
131
+ Sidekiq.logger.error("Setup RSS: #{Process.rss}")
132
+ Sidekiq.redis { |c| c.flushdb }
133
+ start = Time.now
134
+ if ENV["AJ"]
135
+ @iter.times do
136
+ @count.times do |idx|
137
+ LoadJob.perform_later(idx)
138
+ end
139
+ end
140
+ else
141
+ @iter.times do
142
+ arr = Array.new(@count) { |idx| [idx] }
143
+ Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr)
144
+ end
145
+ end
146
+ Sidekiq.logger.warn "Created #{@count * @iter} jobs in #{Time.now - start} sec"
147
+ end
107
148
 
108
- Monitoring = Thread.new do
109
- watchdog("monitor thread") do
110
- while true
111
- sleep 0.2
112
- qsize = Sidekiq.redis do |conn|
113
- conn.llen "queue:default"
149
+ def monitor
150
+ @monitor = Thread.new do
151
+ GC.start
152
+ loop do
153
+ sleep 0.2
154
+ qsize = Sidekiq.redis do |conn|
155
+ conn.llen "queue:default"
156
+ end
157
+ total = qsize
158
+ if total == 0
159
+ ending = Time.now - @start
160
+ size = @iter * @count
161
+ Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
162
+ Sidekiq.logger.error("Ending RSS: #{Process.rss}")
163
+ Sidekiq.logger.error("Now here's the latency for three jobs")
164
+
165
+ if ENV["AJ"]
166
+ LoadJob.perform_later(1, Time.now.to_f)
167
+ LoadJob.perform_later(2, Time.now.to_f)
168
+ LoadJob.perform_later(3, Time.now.to_f)
169
+ else
170
+ LoadWorker.perform_async(1, Time.now.to_f)
171
+ LoadWorker.perform_async(2, Time.now.to_f)
172
+ LoadWorker.perform_async(3, Time.now.to_f)
173
+ end
174
+
175
+ sleep 0.1
176
+ @x.stop
177
+ Process.kill("INT", $$)
178
+ break
179
+ end
114
180
  end
115
- total = qsize
116
- #Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}")
117
- if total == 0
118
- Sidekiq.logger.error("Done, #{iter * count} jobs in #{Time.now - start} sec")
119
- Sidekiq.logger.error("Now here's the latency for three jobs")
181
+ end
182
+ end
120
183
 
121
- LoadWorker.perform_async(1, Time.now.to_f)
122
- LoadWorker.perform_async(2, Time.now.to_f)
123
- LoadWorker.perform_async(3, Time.now.to_f)
184
+ def with_latency(latency, &block)
185
+ Sidekiq.logger.error "Simulating #{latency}ms of latency between Sidekiq and redis"
186
+ if latency > 0
187
+ Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block)
188
+ else
189
+ yield
190
+ end
191
+ end
124
192
 
125
- sleep 0.2
126
- exit(0)
193
+ def run(name)
194
+ Sidekiq.logger.warn("Starting #{name}")
195
+ monitor
196
+
197
+ if ENV["PROFILE"]
198
+ RubyProf.exclude_threads = [@monitor]
199
+ RubyProf.start
200
+ elsif ENV["GC"]
201
+ GC.start
202
+ GC.compact
203
+ GC.disable
204
+ Sidekiq.logger.error("GC Start RSS: #{Process.rss}")
205
+ end
206
+ @start = Time.now
207
+ with_latency(@latency) do
208
+ @x.run
209
+
210
+ while (readable_io = IO.select([@self_read]))
211
+ signal = readable_io.first[0].gets.strip
212
+ handle_signal(signal)
127
213
  end
128
214
  end
215
+ # normal
216
+ rescue Interrupt
217
+ rescue => e
218
+ raise e if $DEBUG
219
+ warn e.message
220
+ warn e.backtrace.join("\n")
221
+ exit 1
222
+ ensure
223
+ @x.stop
129
224
  end
130
- end
131
225
 
132
- begin
133
- #RubyProf::exclude_threads = [ Monitoring ]
134
- #RubyProf.start
135
- fire_event(:startup)
136
- Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
137
- Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
138
- launcher = Sidekiq::Launcher.new(Sidekiq.options)
139
- launcher.run
140
-
141
- while readable_io = IO.select([self_read])
142
- signal = readable_io.first[0].gets.strip
143
- handle_signal(launcher, signal)
226
+ def done
227
+ Sidekiq.logger.error("GC End RSS: #{Process.rss}") if ENV["GC"]
228
+ if ENV["PROFILE"]
229
+ Sidekiq.logger.error("Profiling...")
230
+ result = RubyProf.stop
231
+ printer = RubyProf::GraphHtmlPrinter.new(result)
232
+ printer.print(File.new("output.html", "w"), min_percent: 1)
144
233
  end
145
234
  end
146
- rescue SystemExit => e
147
- #Sidekiq.logger.error("Profiling...")
148
- #result = RubyProf.stop
149
- #printer = RubyProf::GraphHtmlPrinter.new(result)
150
- #printer.print(File.new("output.html", "w"), :min_percent => 1)
151
- # normal
152
- rescue => e
153
- raise e if $DEBUG
154
- STDERR.puts e.message
155
- STDERR.puts e.backtrace.join("\n")
156
- exit 1
157
235
  end
236
+
237
+ ll = Loader.new
238
+ ll.configure
239
+
240
+ if ENV["WARM"]
241
+ ll.setup
242
+ ll.run("warmup")
243
+ end
244
+
245
+ ll.setup
246
+ ll.run("load")
247
+ ll.done
data/bin/sidekiqmon CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'sidekiq/monitor'
3
+ require "sidekiq/monitor"
4
+
5
+ # disable the Redis connection pool logging
6
+ Sidekiq.default_configuration.logger.level = :warn
4
7
 
5
8
  section = "all"
6
9
  section = ARGV[0] if ARGV.size == 1
@@ -0,0 +1,57 @@
1
+ require "rails/generators/named_base"
2
+
3
+ module Sidekiq
4
+ module Generators # :nodoc:
5
+ class JobGenerator < ::Rails::Generators::NamedBase # :nodoc:
6
+ desc "This generator creates a Sidekiq Job in app/sidekiq and a corresponding test"
7
+
8
+ check_class_collision suffix: "Job"
9
+
10
+ def self.default_generator_root
11
+ File.dirname(__FILE__)
12
+ end
13
+
14
+ def create_job_file
15
+ template "job.rb.erb", File.join("app/sidekiq", class_path, "#{file_name}_job.rb")
16
+ end
17
+
18
+ def create_test_file
19
+ return unless test_framework
20
+
21
+ if test_framework == :rspec
22
+ create_job_spec
23
+ else
24
+ create_job_test
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def create_job_spec
31
+ template_file = File.join(
32
+ "spec/sidekiq",
33
+ class_path,
34
+ "#{file_name}_job_spec.rb"
35
+ )
36
+ template "job_spec.rb.erb", template_file
37
+ end
38
+
39
+ def create_job_test
40
+ template_file = File.join(
41
+ "test/sidekiq",
42
+ class_path,
43
+ "#{file_name}_job_test.rb"
44
+ )
45
+ template "job_test.rb.erb", template_file
46
+ end
47
+
48
+ def file_name
49
+ @_file_name ||= super.sub(/_?job\z/i, "")
50
+ end
51
+
52
+ def test_framework
53
+ ::Rails.application.config.generators.options[:rails][:test_framework]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,6 +1,6 @@
1
1
  <% module_namespacing do -%>
2
- class <%= class_name %>Worker
3
- include Sidekiq::Worker
2
+ class <%= class_name %>Job
3
+ include Sidekiq::Job
4
4
 
5
5
  def perform(*args)
6
6
  # Do something
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
  <% module_namespacing do -%>
3
- RSpec.describe <%= class_name %>Worker, type: :worker do
3
+ RSpec.describe <%= class_name %>Job, type: :job do
4
4
  pending "add some examples to (or delete) #{__FILE__}"
5
5
  end
6
6
  <% end -%>
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
  <% module_namespacing do -%>
3
- class <%= class_name %>WorkerTest < Minitest::Test
3
+ class <%= class_name %>JobTest < Minitest::Test
4
4
  def test_example
5
5
  skip "add some examples to (or delete) #{__FILE__}"
6
6
  end