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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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