sidekiq 5.0.1 → 5.2.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.circleci/config.yml +61 -0
- data/.github/issue_template.md +3 -1
- data/.gitignore +2 -0
- data/.travis.yml +6 -13
- data/COMM-LICENSE +11 -9
- data/Changes.md +136 -1
- data/Ent-Changes.md +46 -3
- data/Gemfile +14 -20
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +125 -0
- data/README.md +5 -3
- data/Rakefile +2 -5
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +2 -2
- data/lib/sidekiq.rb +24 -15
- data/lib/sidekiq/api.rb +83 -37
- data/lib/sidekiq/cli.rb +106 -76
- data/lib/sidekiq/client.rb +36 -33
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/delay.rb +23 -2
- data/lib/sidekiq/exception_handler.rb +2 -4
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/job_logger.rb +4 -3
- data/lib/sidekiq/job_retry.rb +51 -24
- data/lib/sidekiq/launcher.rb +18 -12
- data/lib/sidekiq/logging.rb +9 -5
- data/lib/sidekiq/manager.rb +5 -6
- data/lib/sidekiq/middleware/server/active_record.rb +2 -1
- data/lib/sidekiq/processor.rb +85 -48
- data/lib/sidekiq/rails.rb +7 -0
- data/lib/sidekiq/redis_connection.rb +40 -4
- data/lib/sidekiq/scheduled.rb +35 -8
- data/lib/sidekiq/testing.rb +4 -4
- data/lib/sidekiq/util.rb +5 -1
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web.rb +4 -4
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +24 -2
- data/lib/sidekiq/web/helpers.rb +18 -8
- data/lib/sidekiq/web/router.rb +10 -10
- data/lib/sidekiq/worker.rb +39 -22
- data/sidekiq.gemspec +6 -17
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +15 -5
- data/web/assets/stylesheets/application.css +35 -2
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +1 -0
- data/web/locales/en.yml +2 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/ja.yml +5 -3
- data/web/views/_footer.erb +3 -0
- data/web/views/_nav.erb +3 -17
- data/web/views/layout.erb +1 -1
- data/web/views/queue.erb +1 -0
- data/web/views/queues.erb +2 -0
- data/web/views/retries.erb +4 -0
- metadata +20 -156
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -17,7 +17,7 @@ module Sidekiq
|
|
17
17
|
# We need to go through the list one at a time to reduce the risk of something
|
18
18
|
# going wrong between the time jobs are popped from the scheduled queue and when
|
19
19
|
# they are pushed onto a work queue and losing the jobs.
|
20
|
-
while job = conn.zrangebyscore(sorted_set, '-inf'
|
20
|
+
while job = conn.zrangebyscore(sorted_set, '-inf', now, :limit => [0, 1]).first do
|
21
21
|
|
22
22
|
# Pop item off the queue and add it to the work queue. If the job can't be popped from
|
23
23
|
# the queue, it's because another process already popped it so we can move on to the
|
@@ -79,9 +79,7 @@ module Sidekiq
|
|
79
79
|
# Most likely a problem with redis networking.
|
80
80
|
# Punt and try again at the next interval
|
81
81
|
logger.error ex.message
|
82
|
-
ex
|
83
|
-
logger.error(bt)
|
84
|
-
end
|
82
|
+
handle_exception(ex)
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
@@ -95,13 +93,38 @@ module Sidekiq
|
|
95
93
|
# if poll_interval_average hasn't been calculated yet, we can
|
96
94
|
# raise an error trying to reach Redis.
|
97
95
|
logger.error ex.message
|
98
|
-
|
96
|
+
handle_exception(ex)
|
99
97
|
sleep 5
|
100
98
|
end
|
101
99
|
|
102
|
-
# Calculates a random interval that is ±50% the desired average.
|
103
100
|
def random_poll_interval
|
104
|
-
|
101
|
+
# We want one Sidekiq process to schedule jobs every N seconds. We have M processes
|
102
|
+
# and **don't** want to coordinate.
|
103
|
+
#
|
104
|
+
# So in N*M second timespan, we want each process to schedule once. The basic loop is:
|
105
|
+
#
|
106
|
+
# * sleep a random amount within that N*M timespan
|
107
|
+
# * wake up and schedule
|
108
|
+
#
|
109
|
+
# We want to avoid one edge case: imagine a set of 2 processes, scheduling every 5 seconds,
|
110
|
+
# so N*M = 10. Each process decides to randomly sleep 8 seconds, now we've failed to meet
|
111
|
+
# that 5 second average. Thankfully each schedule cycle will sleep randomly so the next
|
112
|
+
# iteration could see each process sleep for 1 second, undercutting our average.
|
113
|
+
#
|
114
|
+
# So below 10 processes, we special case and ensure the processes sleep closer to the average.
|
115
|
+
# In the example above, each process should schedule every 10 seconds on average. We special
|
116
|
+
# case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
|
117
|
+
# As we run more processes, the scheduling interval average will approach an even spread
|
118
|
+
# between 0 and poll interval so we don't need this artifical boost.
|
119
|
+
#
|
120
|
+
if process_count < 10
|
121
|
+
# For small clusters, calculate a random interval that is ±50% the desired average.
|
122
|
+
poll_interval_average * rand + poll_interval_average.to_f / 2
|
123
|
+
else
|
124
|
+
# With 10+ processes, we should have enough randomness to get decent polling
|
125
|
+
# across the entire timespan
|
126
|
+
poll_interval_average * rand
|
127
|
+
end
|
105
128
|
end
|
106
129
|
|
107
130
|
# We do our best to tune the poll interval to the size of the active Sidekiq
|
@@ -125,9 +148,13 @@ module Sidekiq
|
|
125
148
|
# This minimizes a single point of failure by dispersing check-ins but without taxing
|
126
149
|
# Redis if you run many Sidekiq processes.
|
127
150
|
def scaled_poll_interval
|
151
|
+
process_count * Sidekiq.options[:average_scheduled_poll_interval]
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_count
|
128
155
|
pcount = Sidekiq::ProcessSet.new.size
|
129
156
|
pcount = 1 if pcount == 0
|
130
|
-
pcount
|
157
|
+
pcount
|
131
158
|
end
|
132
159
|
|
133
160
|
def initial_wait
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -72,9 +72,7 @@ module Sidekiq
|
|
72
72
|
|
73
73
|
class EmptyQueueError < RuntimeError; end
|
74
74
|
|
75
|
-
|
76
|
-
alias_method :raw_push_real, :raw_push
|
77
|
-
|
75
|
+
module TestingClient
|
78
76
|
def raw_push(payloads)
|
79
77
|
if Sidekiq::Testing.fake?
|
80
78
|
payloads.each do |job|
|
@@ -92,11 +90,13 @@ module Sidekiq
|
|
92
90
|
end
|
93
91
|
true
|
94
92
|
else
|
95
|
-
|
93
|
+
super
|
96
94
|
end
|
97
95
|
end
|
98
96
|
end
|
99
97
|
|
98
|
+
Sidekiq::Client.prepend TestingClient
|
99
|
+
|
100
100
|
module Queues
|
101
101
|
##
|
102
102
|
# The Queues class is only for testing the fake queue implementation.
|
data/lib/sidekiq/util.rb
CHANGED
@@ -46,7 +46,10 @@ module Sidekiq
|
|
46
46
|
@@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
|
47
47
|
end
|
48
48
|
|
49
|
-
def fire_event(event,
|
49
|
+
def fire_event(event, options={})
|
50
|
+
reverse = options[:reverse]
|
51
|
+
reraise = options[:reraise]
|
52
|
+
|
50
53
|
arr = Sidekiq.options[:lifecycle_events][event]
|
51
54
|
arr.reverse! if reverse
|
52
55
|
arr.each do |block|
|
@@ -54,6 +57,7 @@ module Sidekiq
|
|
54
57
|
block.call
|
55
58
|
rescue => ex
|
56
59
|
handle_exception(ex, { context: "Exception during Sidekiq lifecycle event.", event: event })
|
60
|
+
raise ex if reraise
|
57
61
|
end
|
58
62
|
end
|
59
63
|
arr.clear
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web.rb
CHANGED
@@ -19,10 +19,10 @@ require 'rack/session/cookie'
|
|
19
19
|
module Sidekiq
|
20
20
|
class Web
|
21
21
|
ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
|
22
|
-
VIEWS = "#{ROOT}/views"
|
23
|
-
LOCALES = ["#{ROOT}/locales"
|
24
|
-
LAYOUT = "#{VIEWS}/layout.erb"
|
25
|
-
ASSETS = "#{ROOT}/assets"
|
22
|
+
VIEWS = "#{ROOT}/views"
|
23
|
+
LOCALES = ["#{ROOT}/locales"]
|
24
|
+
LAYOUT = "#{VIEWS}/layout.erb"
|
25
|
+
ASSETS = "#{ROOT}/assets"
|
26
26
|
|
27
27
|
DEFAULT_TABS = {
|
28
28
|
"Dashboard" => '',
|
data/lib/sidekiq/web/action.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
class WebAction
|
5
|
-
RACK_SESSION = 'rack.session'
|
5
|
+
RACK_SESSION = 'rack.session'
|
6
6
|
|
7
7
|
attr_accessor :env, :block, :type
|
8
8
|
|
@@ -77,7 +77,7 @@ module Sidekiq
|
|
77
77
|
private
|
78
78
|
|
79
79
|
def _erb(file, locals)
|
80
|
-
locals.each {|k, v| define_singleton_method(k){ v } } if locals
|
80
|
+
locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
|
81
81
|
|
82
82
|
if file.kind_of?(String)
|
83
83
|
ERB.new(file).result(binding)
|
@@ -4,9 +4,24 @@ module Sidekiq
|
|
4
4
|
class WebApplication
|
5
5
|
extend WebRouter
|
6
6
|
|
7
|
-
CONTENT_LENGTH = "Content-Length"
|
8
|
-
CONTENT_TYPE = "Content-Type"
|
7
|
+
CONTENT_LENGTH = "Content-Length"
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
9
|
REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
|
10
|
+
CSP_HEADER = [
|
11
|
+
"default-src 'self' https: http:",
|
12
|
+
"child-src 'self'",
|
13
|
+
"connect-src 'self' https: http: wss: ws:",
|
14
|
+
"font-src 'self' https: http:",
|
15
|
+
"frame-src 'self'",
|
16
|
+
"img-src 'self' https: http: data:",
|
17
|
+
"manifest-src 'self'",
|
18
|
+
"media-src 'self'",
|
19
|
+
"object-src 'none'",
|
20
|
+
"script-src 'self' https: http: 'unsafe-inline'",
|
21
|
+
"style-src 'self' https: http: 'unsafe-inline'",
|
22
|
+
"worker-src 'self'",
|
23
|
+
"base-uri 'self'"
|
24
|
+
].join('; ').freeze
|
10
25
|
|
11
26
|
def initialize(klass)
|
12
27
|
@klass = klass
|
@@ -181,6 +196,12 @@ module Sidekiq
|
|
181
196
|
redirect "#{root_path}retries"
|
182
197
|
end
|
183
198
|
|
199
|
+
post "/retries/all/kill" do
|
200
|
+
Sidekiq::RetrySet.new.kill_all
|
201
|
+
|
202
|
+
redirect "#{root_path}retries"
|
203
|
+
end
|
204
|
+
|
184
205
|
post "/retries/:key" do
|
185
206
|
job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
|
186
207
|
|
@@ -279,6 +300,7 @@ module Sidekiq
|
|
279
300
|
"Content-Type" => "text/html",
|
280
301
|
"Cache-Control" => "no-cache",
|
281
302
|
"Content-Language" => action.locale,
|
303
|
+
"Content-Security-Policy" => CSP_HEADER
|
282
304
|
}
|
283
305
|
|
284
306
|
[200, headers, [resp]]
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -80,7 +80,7 @@ module Sidekiq
|
|
80
80
|
|
81
81
|
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
82
82
|
def user_preferred_languages
|
83
|
-
languages = env['HTTP_ACCEPT_LANGUAGE'
|
83
|
+
languages = env['HTTP_ACCEPT_LANGUAGE']
|
84
84
|
languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
|
85
85
|
locale, quality = language.split(';q=', 2)
|
86
86
|
locale = nil if locale == '*' # Ignore wildcards
|
@@ -121,7 +121,7 @@ module Sidekiq
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def t(msg, options={})
|
124
|
-
string = get_locale[msg] || msg
|
124
|
+
string = get_locale[msg] || strings('en')[msg] || msg
|
125
125
|
if options.empty?
|
126
126
|
string
|
127
127
|
else
|
@@ -148,11 +148,14 @@ module Sidekiq
|
|
148
148
|
end
|
149
149
|
|
150
150
|
def redis_connection
|
151
|
-
Sidekiq.redis
|
151
|
+
Sidekiq.redis do |conn|
|
152
|
+
c = conn.connection
|
153
|
+
"redis://#{c[:location]}/#{c[:db]}"
|
154
|
+
end
|
152
155
|
end
|
153
156
|
|
154
157
|
def namespace
|
155
|
-
|
158
|
+
@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
|
156
159
|
end
|
157
160
|
|
158
161
|
def redis_info
|
@@ -181,7 +184,7 @@ module Sidekiq
|
|
181
184
|
end
|
182
185
|
|
183
186
|
def parse_params(params)
|
184
|
-
score, jid = params.split("-")
|
187
|
+
score, jid = params.split("-", 2)
|
185
188
|
[score.to_f, jid]
|
186
189
|
end
|
187
190
|
|
@@ -204,9 +207,16 @@ module Sidekiq
|
|
204
207
|
end
|
205
208
|
|
206
209
|
def display_args(args, truncate_after_chars = 2000)
|
207
|
-
args
|
208
|
-
|
209
|
-
|
210
|
+
return "Invalid job payload, args is nil" if args == nil
|
211
|
+
return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array)
|
212
|
+
|
213
|
+
begin
|
214
|
+
args.map do |arg|
|
215
|
+
h(truncate(to_display(arg), truncate_after_chars))
|
216
|
+
end.join(", ")
|
217
|
+
rescue
|
218
|
+
"Illegal job arguments: #{h args.inspect}"
|
219
|
+
end
|
210
220
|
end
|
211
221
|
|
212
222
|
def csrf_tag
|
data/lib/sidekiq/web/router.rb
CHANGED
@@ -3,16 +3,16 @@ require 'rack'
|
|
3
3
|
|
4
4
|
module Sidekiq
|
5
5
|
module WebRouter
|
6
|
-
GET = 'GET'
|
7
|
-
DELETE = 'DELETE'
|
8
|
-
POST = 'POST'
|
9
|
-
PUT = 'PUT'
|
10
|
-
PATCH = 'PATCH'
|
11
|
-
HEAD = 'HEAD'
|
6
|
+
GET = 'GET'
|
7
|
+
DELETE = 'DELETE'
|
8
|
+
POST = 'POST'
|
9
|
+
PUT = 'PUT'
|
10
|
+
PATCH = 'PATCH'
|
11
|
+
HEAD = 'HEAD'
|
12
12
|
|
13
|
-
ROUTE_PARAMS = 'rack.route_params'
|
14
|
-
REQUEST_METHOD = 'REQUEST_METHOD'
|
15
|
-
PATH_INFO = 'PATH_INFO'
|
13
|
+
ROUTE_PARAMS = 'rack.route_params'
|
14
|
+
REQUEST_METHOD = 'REQUEST_METHOD'
|
15
|
+
PATH_INFO = 'PATH_INFO'
|
16
16
|
|
17
17
|
def get(path, &block)
|
18
18
|
route(GET, path, &block)
|
@@ -64,7 +64,7 @@ module Sidekiq
|
|
64
64
|
class WebRoute
|
65
65
|
attr_accessor :request_method, :pattern, :block, :name
|
66
66
|
|
67
|
-
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)
|
67
|
+
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/
|
68
68
|
|
69
69
|
def initialize(request_method, pattern, block)
|
70
70
|
@request_method = request_method
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -7,13 +7,13 @@ module Sidekiq
|
|
7
7
|
# Include this module in your worker class and you can easily create
|
8
8
|
# asynchronous jobs:
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# class HardWorker
|
11
|
+
# include Sidekiq::Worker
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
13
|
+
# def perform(*args)
|
14
|
+
# # do some work
|
15
|
+
# end
|
15
16
|
# end
|
16
|
-
# end
|
17
17
|
#
|
18
18
|
# Then in your Rails app, you can do this:
|
19
19
|
#
|
@@ -27,9 +27,9 @@ module Sidekiq
|
|
27
27
|
raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}" if base.ancestors.any? {|c| c.name == 'ActiveJob::Base' }
|
28
28
|
|
29
29
|
base.extend(ClassMethods)
|
30
|
-
base.
|
31
|
-
base.
|
32
|
-
base.
|
30
|
+
base.sidekiq_class_attribute :sidekiq_options_hash
|
31
|
+
base.sidekiq_class_attribute :sidekiq_retry_in_block
|
32
|
+
base.sidekiq_class_attribute :sidekiq_retries_exhausted_block
|
33
33
|
end
|
34
34
|
|
35
35
|
def logger
|
@@ -41,12 +41,18 @@ module Sidekiq
|
|
41
41
|
# SomeWorker.set(queue: 'foo').perform_async(....)
|
42
42
|
#
|
43
43
|
class Setter
|
44
|
-
def initialize(opts)
|
44
|
+
def initialize(klass, opts)
|
45
|
+
@klass = klass
|
45
46
|
@opts = opts
|
46
47
|
end
|
47
48
|
|
49
|
+
def set(options)
|
50
|
+
@opts.merge!(options)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
48
54
|
def perform_async(*args)
|
49
|
-
@
|
55
|
+
@klass.client_push(@opts.merge('args' => args, 'class' => @klass))
|
50
56
|
end
|
51
57
|
|
52
58
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -56,15 +62,16 @@ module Sidekiq
|
|
56
62
|
now = Time.now.to_f
|
57
63
|
ts = (int < 1_000_000_000 ? now + int : int)
|
58
64
|
|
59
|
-
@opts.merge
|
65
|
+
payload = @opts.merge('class' => @klass, 'args' => args, 'at' => ts)
|
60
66
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
61
|
-
|
62
|
-
@
|
67
|
+
payload.delete('at') if ts <= now
|
68
|
+
@klass.client_push(payload)
|
63
69
|
end
|
64
70
|
alias_method :perform_at, :perform_in
|
65
71
|
end
|
66
72
|
|
67
73
|
module ClassMethods
|
74
|
+
ACCESSOR_MUTEX = Mutex.new
|
68
75
|
|
69
76
|
def delay(*args)
|
70
77
|
raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
|
@@ -79,11 +86,11 @@ module Sidekiq
|
|
79
86
|
end
|
80
87
|
|
81
88
|
def set(options)
|
82
|
-
Setter.new(options
|
89
|
+
Setter.new(self, options)
|
83
90
|
end
|
84
91
|
|
85
92
|
def perform_async(*args)
|
86
|
-
client_push('class'
|
93
|
+
client_push('class' => self, 'args' => args)
|
87
94
|
end
|
88
95
|
|
89
96
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -93,10 +100,10 @@ module Sidekiq
|
|
93
100
|
now = Time.now.to_f
|
94
101
|
ts = (int < 1_000_000_000 ? now + int : int)
|
95
102
|
|
96
|
-
item = { 'class'
|
103
|
+
item = { 'class' => self, 'args' => args, 'at' => ts }
|
97
104
|
|
98
105
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
99
|
-
item.delete('at'
|
106
|
+
item.delete('at') if ts <= now
|
100
107
|
|
101
108
|
client_push(item)
|
102
109
|
end
|
@@ -133,7 +140,7 @@ module Sidekiq
|
|
133
140
|
end
|
134
141
|
|
135
142
|
def client_push(item) # :nodoc:
|
136
|
-
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'
|
143
|
+
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
|
137
144
|
# stringify
|
138
145
|
item.keys.each do |key|
|
139
146
|
item[key.to_s] = item.delete(key)
|
@@ -142,15 +149,23 @@ module Sidekiq
|
|
142
149
|
Sidekiq::Client.new(pool).push(item)
|
143
150
|
end
|
144
151
|
|
145
|
-
def
|
152
|
+
def sidekiq_class_attribute(*attrs)
|
146
153
|
instance_reader = true
|
147
154
|
instance_writer = true
|
148
155
|
|
149
156
|
attrs.each do |name|
|
157
|
+
synchronized_getter = "__synchronized_#{name}"
|
158
|
+
|
150
159
|
singleton_class.instance_eval do
|
151
160
|
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
152
161
|
end
|
153
|
-
|
162
|
+
|
163
|
+
define_singleton_method(synchronized_getter) { nil }
|
164
|
+
singleton_class.class_eval do
|
165
|
+
private(synchronized_getter)
|
166
|
+
end
|
167
|
+
|
168
|
+
define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
|
154
169
|
|
155
170
|
ivar = "@#{name}"
|
156
171
|
|
@@ -160,8 +175,10 @@ module Sidekiq
|
|
160
175
|
end
|
161
176
|
define_singleton_method("#{name}=") do |val|
|
162
177
|
singleton_class.class_eval do
|
163
|
-
|
164
|
-
|
178
|
+
ACCESSOR_MUTEX.synchronize do
|
179
|
+
undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
|
180
|
+
define_method(synchronized_getter) { val }
|
181
|
+
end
|
165
182
|
end
|
166
183
|
|
167
184
|
if singleton_class?
|