concurrent-ruby 0.9.0.pre3-java → 0.9.1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- require 'concurrent/atomic/thread_local_var/weak_key_map'
1
+ require 'thread'
2
2
 
3
3
  module Concurrent
4
4
 
@@ -56,45 +56,29 @@ module Concurrent
56
56
  #
57
57
  # @return [Object] the current value
58
58
  def value
59
- value = get
60
-
61
- if value.nil?
62
- @default
63
- elsif value == NIL_SENTINEL
64
- nil
65
- else
66
- value
67
- end
59
+ raise NotImplementedError
68
60
  end
69
61
 
70
62
  # @!macro [attach] thread_local_var_method_set
71
63
  #
72
64
  # Sets the current thread's copy of this thread-local variable to the specified value.
73
- #
65
+ #
74
66
  # @param [Object] value the value to set
75
67
  # @return [Object] the new value
76
68
  def value=(value)
77
- bind value
69
+ raise NotImplementedError
78
70
  end
79
71
 
80
72
  # @!macro [attach] thread_local_var_method_bind
81
73
  #
82
74
  # Bind the given value to thread local storage during
83
75
  # execution of the given block.
84
- #
76
+ #
85
77
  # @param [Object] value the value to bind
86
78
  # @yield the operation to be performed with the bound variable
87
79
  # @return [Object] the value
88
80
  def bind(value, &block)
89
- if value.nil?
90
- stored_value = NIL_SENTINEL
91
- else
92
- stored_value = value
93
- end
94
-
95
- set(stored_value, &block)
96
-
97
- value
81
+ raise NotImplementedError
98
82
  end
99
83
 
100
84
  protected
@@ -103,46 +87,171 @@ module Concurrent
103
87
  def allocate_storage
104
88
  raise NotImplementedError
105
89
  end
90
+ end
91
+
92
+ # @!visibility private
93
+ # @!macro internal_implementation_note
94
+ class RubyThreadLocalVar < AbstractThreadLocalVar
95
+
96
+ # Each thread has a (lazily initialized) array of thread-local variable values
97
+ # Each time a new thread-local var is created, we allocate an "index" for it
98
+ # For example, if the allocated index is 1, that means slot #1 in EVERY
99
+ # thread's thread-local array will be used for the value of that TLV
100
+ #
101
+ # The good thing about using a per-THREAD structure to hold values, rather
102
+ # than a per-TLV structure, is that no synchronization is needed when
103
+ # reading and writing those values (since the structure is only ever
104
+ # accessed by a single thread)
105
+ #
106
+ # Of course, when a TLV is GC'd, 1) we need to recover its index for use
107
+ # by other new TLVs (otherwise the thread-local arrays could get bigger
108
+ # and bigger with time), and 2) we need to null out all the references
109
+ # held in the now-unused slots (both to avoid blocking GC of those objects,
110
+ # and also to prevent "stale" values from being passed on to a new TLV
111
+ # when the index is reused)
112
+ # Because we need to null out freed slots, we need to keep references to
113
+ # ALL the thread-local arrays -- ARRAYS is for that
114
+ # But when a Thread is GC'd, we need to drop the reference to its thread-local
115
+ # array, so we don't leak memory
106
116
 
107
117
  # @!visibility private
108
- def get
109
- raise NotImplementedError
118
+ FREE = []
119
+ LOCK = Mutex.new
120
+ ARRAYS = {} # used as a hash set
121
+ @@next = 0
122
+ private_constant :FREE, :LOCK, :ARRAYS
123
+
124
+ # @!macro [attach] thread_local_var_method_initialize
125
+ #
126
+ # Creates a thread local variable.
127
+ #
128
+ # @param [Object] default the default value when otherwise unset
129
+ def initialize(default = nil)
130
+ @default = default
131
+ allocate_storage
110
132
  end
111
133
 
112
- # @!visibility private
113
- def set(value)
114
- raise NotImplementedError
134
+ # @!macro thread_local_var_method_get
135
+ def value
136
+ if array = get_threadlocal_array
137
+ value = array[@index]
138
+ if value.nil?
139
+ @default
140
+ elsif value.equal?(NIL_SENTINEL)
141
+ nil
142
+ else
143
+ value
144
+ end
145
+ else
146
+ @default
147
+ end
115
148
  end
116
- end
117
149
 
118
- # @!visibility private
119
- # @!macro internal_implementation_note
120
- class RubyThreadLocalVar < AbstractThreadLocalVar
150
+ # @!macro thread_local_var_method_set
151
+ def value=(value)
152
+ me = Thread.current
153
+ # We could keep the thread-local arrays in a hash, keyed by Thread
154
+ # But why? That would require locking
155
+ # Using Ruby's built-in thread-local storage is faster
156
+ unless array = get_threadlocal_array(me)
157
+ array = set_threadlocal_array([], me)
158
+ LOCK.synchronize { ARRAYS[array.object_id] = array }
159
+ ObjectSpace.define_finalizer(me, self.class.thread_finalizer(array))
160
+ end
161
+ array[@index] = (value.nil? ? NIL_SENTINEL : value)
162
+ value
163
+ end
164
+
165
+ # @!macro thread_local_var_method_bind
166
+ def bind(value, &block)
167
+ if block_given?
168
+ old_value = self.value
169
+ begin
170
+ self.value = value
171
+ yield
172
+ ensure
173
+ self.value = old_value
174
+ end
175
+ end
176
+ end
121
177
 
122
178
  protected
123
179
 
124
180
  # @!visibility private
125
181
  def allocate_storage
126
- @storage = WeakKeyMap.new
182
+ @index = LOCK.synchronize do
183
+ FREE.pop || begin
184
+ result = @@next
185
+ @@next += 1
186
+ result
187
+ end
188
+ end
189
+ ObjectSpace.define_finalizer(self, self.class.threadlocal_finalizer(@index))
127
190
  end
128
191
 
129
192
  # @!visibility private
130
- def get
131
- @storage[Thread.current]
193
+ def self.threadlocal_finalizer(index)
194
+ proc do
195
+ LOCK.synchronize do
196
+ FREE.push(index)
197
+ # The cost of GC'ing a TLV is linear in the number of threads using TLVs
198
+ # But that is natural! More threads means more storage is used per TLV
199
+ # So naturally more CPU time is required to free more storage
200
+ ARRAYS.each_value do |array|
201
+ array[index] = nil
202
+ end
203
+ end
204
+ end
132
205
  end
133
206
 
134
207
  # @!visibility private
135
- def set(value)
136
- key = Thread.current
208
+ def self.thread_finalizer(array)
209
+ proc do
210
+ LOCK.synchronize do
211
+ # The thread which used this thread-local array is now gone
212
+ # So don't hold onto a reference to the array (thus blocking GC)
213
+ ARRAYS.delete(array.object_id)
214
+ end
215
+ end
216
+ end
137
217
 
138
- @storage[key] = value
218
+ private
139
219
 
140
- if block_given?
141
- begin
142
- yield
143
- ensure
144
- @storage.delete(key)
220
+ if Thread.instance_methods.include?(:thread_variable_get)
221
+
222
+ def get_threadlocal_array(thread = Thread.current)
223
+ thread.thread_variable_get(:__threadlocal_array__)
224
+ end
225
+
226
+ def set_threadlocal_array(array, thread = Thread.current)
227
+ thread.thread_variable_set(:__threadlocal_array__, array)
228
+ end
229
+
230
+ else
231
+
232
+ def get_threadlocal_array(thread = Thread.current)
233
+ thread[:__threadlocal_array__]
234
+ end
235
+
236
+ def set_threadlocal_array(array, thread = Thread.current)
237
+ thread[:__threadlocal_array__] = array
238
+ end
239
+ end
240
+
241
+ # This exists only for use in testing
242
+ # @!visibility private
243
+ def value_for(thread)
244
+ if array = get_threadlocal_array(thread)
245
+ value = array[@index]
246
+ if value.nil?
247
+ @default
248
+ elsif value.equal?(NIL_SENTINEL)
249
+ nil
250
+ else
251
+ value
145
252
  end
253
+ else
254
+ @default
146
255
  end
147
256
  end
148
257
  end
@@ -153,21 +262,42 @@ module Concurrent
153
262
  # @!macro internal_implementation_note
154
263
  class JavaThreadLocalVar < AbstractThreadLocalVar
155
264
 
156
- protected
265
+ # @!macro thread_local_var_method_get
266
+ def value
267
+ value = @var.get
157
268
 
158
- # @!visibility private
159
- def allocate_storage
160
- @var = java.lang.ThreadLocal.new
269
+ if value.nil?
270
+ @default
271
+ elsif value == NIL_SENTINEL
272
+ nil
273
+ else
274
+ value
275
+ end
161
276
  end
162
277
 
163
- # @!visibility private
164
- def get
165
- @var.get
278
+ # @!macro thread_local_var_method_set
279
+ def value=(value)
280
+ @var.set(value)
281
+ end
282
+
283
+ # @!macro thread_local_var_method_bind
284
+ def bind(value, &block)
285
+ if block_given?
286
+ old_value = @var.get
287
+ begin
288
+ @var.set(value)
289
+ yield
290
+ ensure
291
+ @var.set(old_value)
292
+ end
293
+ end
166
294
  end
167
295
 
296
+ protected
297
+
168
298
  # @!visibility private
169
- def set(value)
170
- @var.set(value)
299
+ def allocate_storage
300
+ @var = java.lang.ThreadLocal.new
171
301
  end
172
302
  end
173
303
  end
@@ -100,7 +100,7 @@ module Concurrent
100
100
  end
101
101
 
102
102
  def duplicate_observers
103
- synchronize { observers = @observers.dup }
103
+ synchronize { @observers.dup }
104
104
  end
105
105
 
106
106
  def notify_to(observers, *args)
@@ -10,18 +10,25 @@ module Concurrent
10
10
  include Concern::Logging
11
11
 
12
12
  def deprecated(message, strip = 2)
13
- caller_line = caller(strip).first
14
- klass = if Class === self
13
+ caller_line = caller(strip).first if strip > 0
14
+ klass = if Module === self
15
15
  self
16
16
  else
17
17
  self.class
18
18
  end
19
- log WARN, klass.to_s, format("[DEPRECATED] %s\ncalled on: %s", message, caller_line)
19
+ message = if strip > 0
20
+ format("[DEPRECATED] %s\ncalled on: %s", message, caller_line)
21
+ else
22
+ format('[DEPRECATED] %s', message)
23
+ end
24
+ log WARN, klass.to_s, message
20
25
  end
21
26
 
22
27
  def deprecated_method(old_name, new_name)
23
28
  deprecated "`#{old_name}` is deprecated and it'll removed in next release, use `#{new_name}` instead", 3
24
29
  end
30
+
31
+ extend self
25
32
  end
26
33
  end
27
34
  end
@@ -15,6 +15,8 @@ module Concurrent
15
15
  # @param [String, nil] message when nil block is used to generate the message
16
16
  # @yieldreturn [String] a message
17
17
  def log(level, progname, message = nil, &block)
18
+ #NOTE: Cannot require 'concurrent/configuration' above due to circular references.
19
+ # Assume that the gem has been initialized if we've gotten this far.
18
20
  (@logger || Concurrent.global_logger).call level, progname, message, &block
19
21
  rescue => error
20
22
  $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
@@ -1,9 +1,13 @@
1
1
  require 'thread'
2
- require 'concurrent/atomics'
2
+ require 'concurrent/delay'
3
3
  require 'concurrent/errors'
4
- require 'concurrent/executors'
4
+ require 'concurrent/atomic/atomic_reference'
5
5
  require 'concurrent/concern/deprecation'
6
6
  require 'concurrent/concern/logging'
7
+ require 'concurrent/executor/timer_set'
8
+ require 'concurrent/executor/immediate_executor'
9
+ require 'concurrent/executor/fixed_thread_pool'
10
+ require 'concurrent/executor/thread_pool_executor'
7
11
  require 'concurrent/utility/at_exit'
8
12
  require 'concurrent/utility/processor_counter'
9
13
 
@@ -11,37 +15,6 @@ module Concurrent
11
15
  extend Concern::Logging
12
16
  extend Concern::Deprecation
13
17
 
14
- # Suppresses all output when used for logging.
15
- NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
16
-
17
- # @!visibility private
18
- GLOBAL_LOGGER = AtomicReference.new(NULL_LOGGER)
19
- private_constant :GLOBAL_LOGGER
20
-
21
- # @!visibility private
22
- GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor(auto_terminate: true) }
23
- private_constant :GLOBAL_FAST_EXECUTOR
24
-
25
- # @!visibility private
26
- GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor(auto_terminate: true) }
27
- private_constant :GLOBAL_IO_EXECUTOR
28
-
29
- # @!visibility private
30
- GLOBAL_TIMER_SET = Delay.new { TimerSet.new(auto_terminate: true) }
31
- private_constant :GLOBAL_TIMER_SET
32
-
33
- # @!visibility private
34
- GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new
35
- private_constant :GLOBAL_IMMEDIATE_EXECUTOR
36
-
37
- def self.global_logger
38
- GLOBAL_LOGGER.value
39
- end
40
-
41
- def self.global_logger=(value)
42
- GLOBAL_LOGGER.value = value
43
- end
44
-
45
18
  # @return [Logger] Logger with provided level and output.
46
19
  def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
47
20
  logger = Logger.new(output)
@@ -62,17 +35,48 @@ module Concurrent
62
35
  progname,
63
36
  formatted_message
64
37
  end
65
- logger
38
+
39
+ lambda do |loglevel, progname, message = nil, &block|
40
+ logger.add loglevel, message, progname, &block
41
+ end
66
42
  end
67
43
 
68
44
  # Use logger created by #create_stdlib_logger to log concurrent-ruby messages.
69
45
  def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr)
70
- logger = create_stdlib_logger level, output
71
- Concurrent.global_logger = lambda do |level, progname, message = nil, &block|
72
- logger.add level, message, progname, &block
73
- end
46
+ Concurrent.global_logger = create_stdlib_logger level, output
74
47
  end
75
48
 
49
+ # Suppresses all output when used for logging.
50
+ NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
51
+
52
+ # @!visibility private
53
+ GLOBAL_LOGGER = AtomicReference.new(create_stdlib_logger(Logger::WARN))
54
+ private_constant :GLOBAL_LOGGER
55
+
56
+ def self.global_logger
57
+ GLOBAL_LOGGER.value
58
+ end
59
+
60
+ def self.global_logger=(value)
61
+ GLOBAL_LOGGER.value = value
62
+ end
63
+
64
+ # @!visibility private
65
+ GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor(auto_terminate: true) }
66
+ private_constant :GLOBAL_FAST_EXECUTOR
67
+
68
+ # @!visibility private
69
+ GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor(auto_terminate: true) }
70
+ private_constant :GLOBAL_IO_EXECUTOR
71
+
72
+ # @!visibility private
73
+ GLOBAL_TIMER_SET = Delay.new { TimerSet.new(auto_terminate: true) }
74
+ private_constant :GLOBAL_TIMER_SET
75
+
76
+ # @!visibility private
77
+ GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new
78
+ private_constant :GLOBAL_IMMEDIATE_EXECUTOR
79
+
76
80
  # Disables AtExit handlers including pool auto-termination handlers.
77
81
  # When disabled it will be the application programmer's responsibility
78
82
  # to ensure that the handlers are shutdown properly prior to application
@@ -275,4 +279,18 @@ module Concurrent
275
279
  def self.configure
276
280
  yield(configuration)
277
281
  end
282
+
283
+ # for dependency reasons this check cannot be in concurrent/synchronization
284
+ if Concurrent.on_jruby?
285
+ require 'java'
286
+
287
+ version_string = java.lang.System.getProperties['java.runtime.version']
288
+ version = version_string.split('.', 3)[0..1].map(&:to_i)
289
+ if (version <=> [1, 8]) < 0
290
+ deprecated <<-TXT.gsub(/^\s*\|/, '').chop, 0
291
+ |Java 7 is deprecated, please use Java 8.
292
+ |Java 7 support is only best effort, it may not work. It will be removed in next release (1.0).
293
+ TXT
294
+ end
295
+ end
278
296
  end