concurrent-ruby 1.1.4 → 1.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: baa8a50b8b08ea03ec40438bf1e921d86a28c2ccc1292535bf9d72aab66fb659
4
- data.tar.gz: cf77c2327c6527a2be4476c2e6464c2b90e86610ecf3543bcee5df5e2c6aa3b0
3
+ metadata.gz: 96f32c31090c8a0547e4ee452739bdc993aad1256e2c89cb9a46d7f7e7b5762a
4
+ data.tar.gz: 307d26bde3add56b7e2b37fb0b016c7726a3ba0755dcfec8202fb0760e4ac485
5
5
  SHA512:
6
- metadata.gz: e9a34a06cc1efb94d36d986369efefb8df61bfe1bd4b0bf960ab84028bf68e6a60636fcaa453490b38c5ced3d8b628e62f92e2524dd08ac1912ccbb74ba40aeb
7
- data.tar.gz: 51790fe6686917f9a11e5e8fc82af16959bdc7ef8111348bfaad010ad1babc97cfde0048407f98cf11002e789a53e5eddff65a58977f87c4825fab06d4a20a11
6
+ metadata.gz: b8cff93c6ef396aebbe5cf69734d9a91f0351e24b5c1846ae105b055254f972cd9d6bb99b90ce10d387627ed43c4e21db9ce47d1587e10278e80e33a53f8d4c4
7
+ data.tar.gz: f367f097759eedb8e18b0117543176c9f30ab29552888fcedab0baaea355cee3d100d2559dd16cf54483778545097f8b3340bebe44b0e3ab2a51c1af3830f5e1
@@ -1,5 +1,18 @@
1
1
  ## Current
2
2
 
3
+ ## Release v1.1.5, edge v0.5.0 (10 mar 2019)
4
+
5
+ concurrent-ruby:
6
+
7
+ * fix potential leak of context on JRuby and Java 7
8
+
9
+ concurrent-ruby-edge:
10
+
11
+ * Add finalized Concurrent::Cancellation
12
+ * Add finalized Concurrent::Throttle
13
+ * Add finalized Concurrent::Promises::Channel
14
+ * Add new Concurrent::ErlangActor
15
+
3
16
  ## Release v1.1.4 (14 Dec 2018)
4
17
 
5
18
  * (#780) Remove java_alias of 'submit' method of Runnable to let executor service work on java 11
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- require File.join(File.dirname(__FILE__ ), 'lib/concurrent/version')
3
+ require File.join(File.dirname(__FILE__), 'lib/concurrent/version')
4
+ require File.join(File.dirname(__FILE__ ), 'lib-edge/concurrent/edge/version')
4
5
 
5
6
  no_path = ENV['NO_PATH']
6
7
  options = no_path ? {} : { path: '.' }
@@ -11,26 +12,27 @@ gem 'concurrent-ruby-ext', Concurrent::VERSION, options.merge(platform: :mri)
11
12
 
12
13
  group :development do
13
14
  gem 'rake', '~> 12.0'
14
- gem 'rake-compiler', '~> 1.0'
15
- gem 'rake-compiler-dock', '~> 0.6.0'
15
+ gem 'rake-compiler', '~> 1.0', '>= 1.0.7'
16
+ gem 'rake-compiler-dock', '~> 0.7.0'
16
17
  gem 'pry', '~> 0.11', platforms: :mri
17
18
  end
18
19
 
19
20
  group :documentation, optional: true do
20
- gem 'yard', '~> 0.9.0', :require => false
21
+ gem 'yard', '~> 0.9.0', require: false
21
22
  gem 'redcarpet', '~> 3.0', platforms: :mri # understands github markdown
22
- gem 'md-ruby-eval', '~> 0.3'
23
+ gem 'md-ruby-eval', '~> 0.6'
23
24
  end
24
25
 
25
26
  group :testing do
26
27
  gem 'rspec', '~> 3.7'
27
28
  gem 'timecop', '~> 0.7.4'
29
+ gem 'sigdump', require: false
28
30
  end
29
31
 
30
32
  # made opt-in since it will not install on jruby 1.7
31
33
  group :coverage, optional: !ENV['COVERAGE'] do
32
- gem 'simplecov', '~> 0.10.0', :require => false
33
- gem 'coveralls', '~> 0.8.2', :require => false
34
+ gem 'simplecov', '~> 0.16.0', require: false
35
+ gem 'coveralls', '~> 0.8.2', require: false
34
36
  end
35
37
 
36
38
  group :benchmarks, optional: true do
data/README.md CHANGED
@@ -42,7 +42,7 @@ appreciate your help. Would you like to contribute? Great! Have a look at
42
42
  ## Thread Safety
43
43
 
44
44
  *Concurrent Ruby makes one of the strongest thread safety guarantees of any Ruby concurrency
45
- library, providing consistent behavior and guarantees on all three of the main Ruby interpreters
45
+ library, providing consistent behavior and guarantees on all four of the main Ruby interpreters
46
46
  (MRI/CRuby, JRuby, Rubinius, TruffleRuby).*
47
47
 
48
48
  Every abstraction in this library is thread safe. Specific thread safety guarantees are documented
@@ -224,6 +224,35 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
224
224
  *Status: will be moved to core soon.*
225
225
  * [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/LockFreeStack.html)
226
226
  *Status: missing documentation and tests.*
227
+ * [Promises::Channel](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises/Channel.html)
228
+ A first in first out channel that accepts messages with push family of methods and returns
229
+ messages with pop family of methods.
230
+ Pop and push operations can be represented as futures, see `#pop_op` and `#push_op`.
231
+ The capacity of the channel can be limited to support back pressure, use capacity option in `#initialize`.
232
+ `#pop` method blocks ans `#pop_op` returns pending future if there is no message in the channel.
233
+ If the capacity is limited the `#push` method blocks and `#push_op` returns pending future.
234
+ * [Cancellation](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Cancellation.html)
235
+ The Cancellation abstraction provides cooperative cancellation.
236
+
237
+ The standard methods `Thread#raise` of `Thread#kill` available in Ruby
238
+ are very dangerous (see linked the blog posts bellow).
239
+ Therefore concurrent-ruby provides an alternative.
240
+
241
+ * <https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/>
242
+ * <http://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/>
243
+ * <http://blog.headius.com/2008/02/rubys-threadraise-threadkill-timeoutrb.html>
244
+
245
+ It provides an object which represents a task which can be executed,
246
+ the task has to get the reference to the object and periodically cooperatively check that it is not cancelled.
247
+ Good practices to make tasks cancellable:
248
+ * check cancellation every cycle of a loop which does significant work,
249
+ * do all blocking actions in a loop with a timeout then on timeout check cancellation
250
+ and if ok block again with the timeout
251
+ * [Throttle](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Throttle.html)
252
+ A tool managing concurrency level of tasks.
253
+ * [ErlangActor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ErlangActor.html)
254
+ Actor implementation which precisely matches Erlang actor behaviour.
255
+ Requires at least Ruby 2.1 otherwise it's not loaded.
227
256
 
228
257
  ## Supported Ruby versions
229
258
 
@@ -339,11 +368,14 @@ and to the past maintainers
339
368
  * [Paweł Obrok](https://github.com/obrok)
340
369
  * [Lucas Allan](https://github.com/lucasallan)
341
370
 
371
+ and to [Ruby Association](https://www.ruby.or.jp/en/) for sponsoring a project
372
+ ["Enhancing Ruby’s concurrency tooling"](https://www.ruby.or.jp/en/news/20181106) in 2018.
373
+
342
374
  ## License and Copyright
343
375
 
344
376
  *Concurrent Ruby* is free software released under the
345
377
  [MIT License](http://www.opensource.org/licenses/MIT).
346
378
 
347
- The *Concurrent Ruby* [logo](https://github.com/ruby-concurrency/concurrent-ruby/wiki/Logo) was
379
+ The *Concurrent Ruby* [logo](https://raw.githubusercontent.com/ruby-concurrency/concurrent-ruby/master/docs-source/logo/concurrent-ruby-logo-300x300.png) was
348
380
  designed by [David Jones](https://twitter.com/zombyboy). It is Copyright &copy; 2014
349
381
  [Jerry D'Antonio](https://twitter.com/jerrydantonio). All Rights Reserved.
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env rake
2
-
3
1
  require_relative 'lib/concurrent/version'
4
2
  require_relative 'lib/concurrent/utility/engine'
5
3
 
@@ -18,43 +16,7 @@ edge_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-edge.
18
16
 
19
17
  require 'rake/javaextensiontask'
20
18
 
21
- class ConcurrentRubyJavaExtensionTask < Rake::JavaExtensionTask
22
- def java_classpath_arg(*args)
23
- jruby_cpath = nil
24
-
25
- if RUBY_PLATFORM =~ /java/
26
- begin
27
- cpath = Java::java.lang.System.getProperty('java.class.path').split(File::PATH_SEPARATOR)
28
- cpath += Java::java.lang.System.getProperty('sun.boot.class.path').split(File::PATH_SEPARATOR)
29
- jruby_cpath = cpath.compact.join(File::PATH_SEPARATOR)
30
- rescue => e
31
- end
32
-
33
- unless jruby_cpath
34
- libdir = RbConfig::CONFIG['libdir']
35
- if libdir.start_with? "classpath:"
36
- raise 'Cannot build with jruby-complete'
37
- end
38
- jruby_cpath = File.join(libdir, "jruby.jar")
39
- end
40
- end
41
-
42
- unless jruby_cpath
43
- jruby_home = ENV['JRUBY_HOME']
44
- if jruby_home
45
- candidate = File.join(jruby_home, 'lib', 'jruby.jar')
46
- jruby_cpath = candidate if File.exist? candidate
47
- end
48
- end
49
-
50
- raise "jruby.jar path not found" unless jruby_cpath
51
-
52
- jruby_cpath += File::PATH_SEPARATOR + args.join(File::PATH_SEPARATOR) unless args.empty?
53
- jruby_cpath ? "-cp \"#{jruby_cpath}\"" : ""
54
- end
55
- end
56
-
57
- ConcurrentRubyJavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext|
19
+ Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext|
58
20
  ext.ext_dir = 'ext/concurrent-ruby'
59
21
  ext.lib_dir = 'lib/concurrent'
60
22
  end
@@ -80,9 +42,11 @@ namespace :repackage do
80
42
  # store gems in vendor cache for docker
81
43
  sh 'bundle package'
82
44
 
83
- # needed only if the jar is built outside of docker
45
+ # build only the jar file not the whole gem for java platform, the jar is part the concurrent-ruby-x.y.z.gem
84
46
  Rake::Task['lib/concurrent/concurrent_ruby.jar'].invoke
85
- RakeCompilerDock.exec 'support/cross_building.sh'
47
+
48
+ # build all gem files
49
+ RakeCompilerDock.sh 'bundle install --local && bundle exec rake cross native package --trace'
86
50
  end
87
51
  end
88
52
  end
@@ -102,15 +66,14 @@ begin
102
66
 
103
67
  RSpec::Core::RakeTask.new(:spec)
104
68
 
105
- options = %w[ --color
106
- --backtrace
107
- --seed 1
108
- --format documentation
109
- --tag ~notravis ]
110
-
111
69
  namespace :spec do
112
70
  desc '* Configured for ci'
113
71
  RSpec::Core::RakeTask.new(:ci) do |t|
72
+ options = %w[ --color
73
+ --backtrace
74
+ --order defined
75
+ --format documentation
76
+ --tag ~notravis ]
114
77
  t.rspec_opts = [*options].join(' ')
115
78
  end
116
79
 
@@ -135,7 +98,7 @@ rescue LoadError => e
135
98
  puts 'RSpec is not installed, skipping test task definitions: ' + e.message
136
99
  end
137
100
 
138
- current_yard_version_name = [*Concurrent::VERSION.split('.')[0..1], 'x'].join('.')
101
+ current_yard_version_name = Concurrent::VERSION
139
102
 
140
103
  begin
141
104
  require 'yard'
@@ -185,10 +148,19 @@ begin
185
148
  end
186
149
 
187
150
  define_yard_task = -> name do
151
+ output_dir = "docs/#{name}"
152
+
153
+ removal_name = "remove.#{name}"
154
+ task removal_name do
155
+ Dir.chdir __dir__ do
156
+ FileUtils.rm_rf output_dir
157
+ end
158
+ end
159
+
188
160
  desc "* of #{name} into subdir #{name}"
189
161
  YARD::Rake::YardocTask.new(name) do |yard|
190
162
  yard.options.push(
191
- '--output-dir', "docs/#{name}",
163
+ '--output-dir', output_dir,
192
164
  '--main', 'tmp/README.md',
193
165
  *common_yard_options)
194
166
  yard.files = ['./lib/**/*.rb',
@@ -197,10 +169,11 @@ begin
197
169
  '-',
198
170
  'docs-source/thread_pools.md',
199
171
  'docs-source/promises.out.md',
172
+ 'docs-source/medium-example.out.rb',
200
173
  'LICENSE.md',
201
174
  'CHANGELOG.md']
202
175
  end
203
- Rake::Task[name].prerequisites.push 'yard:eval_md', 'yard:update_readme'
176
+ Rake::Task[name].prerequisites.push removal_name, 'yard:eval_md', 'yard:update_readme'
204
177
  end
205
178
 
206
179
  define_yard_task.call current_yard_version_name
@@ -223,7 +196,15 @@ begin
223
196
  begin
224
197
  FileUtils.cp_r 'docs', 'docs-copy', verbose: true
225
198
  Rake::Task["yard:#{name}"].invoke
226
- sh 'diff -r docs/ docs-copy/'
199
+ sh 'diff -r docs/ docs-copy/' do |ok, res|
200
+ unless ok
201
+ begin
202
+ STDOUT.puts 'Command failed. Continue? (y/n)'
203
+ input = STDIN.gets.strip.downcase
204
+ end until %w(y n).include?(input)
205
+ exit 1 if input == 'n'
206
+ end
207
+ end
227
208
  ensure
228
209
  FileUtils.rm_rf 'docs-copy', verbose: true
229
210
  end
@@ -275,7 +256,7 @@ namespace :release do
275
256
  end
276
257
 
277
258
  desc '* build all *.gem files necessary for release'
278
- task :build => 'repackage:all'
259
+ task :build => [:clobber, 'repackage:all']
279
260
 
280
261
  desc '* test actual installed gems instead of cloned repository on MRI and JRuby'
281
262
  task :test do
@@ -23,6 +23,7 @@ import java.lang.reflect.Method;
23
23
  public class SynchronizationLibrary implements Library {
24
24
 
25
25
  private static final Unsafe UNSAFE = loadUnsafe();
26
+ private static final boolean FULL_FENCE = supportsFences();
26
27
 
27
28
  private static Unsafe loadUnsafe() {
28
29
  try {
@@ -140,17 +141,17 @@ public class SynchronizationLibrary implements Library {
140
141
  // volatile threadContext is used as a memory barrier per the JVM memory model happens-before semantic
141
142
  // on volatile fields. any volatile field could have been used but using the thread context is an
142
143
  // attempt to avoid code elimination.
143
- private static volatile ThreadContext threadContext = null;
144
+ private static volatile int volatileField;
144
145
 
145
146
  @JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC)
146
147
  public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject self) {
147
148
  // Prevent reordering of ivar writes with publication of this instance
148
- if (!supportsFences()) {
149
+ if (!FULL_FENCE) {
149
150
  // Assuming that following volatile read and write is not eliminated it simulates fullFence.
150
151
  // If it's eliminated it'll cause problems only on non-x86 platforms.
151
152
  // http://shipilev.net/blog/2014/jmm-pragmatics/#_happens_before_test_your_understanding
152
- final ThreadContext oldContext = threadContext;
153
- threadContext = context;
153
+ final int volatileRead = volatileField;
154
+ volatileField = context.getLine();
154
155
  } else {
155
156
  UNSAFE.fullFence();
156
157
  }
@@ -163,9 +164,9 @@ public class SynchronizationLibrary implements Library {
163
164
  IRubyObject self,
164
165
  IRubyObject name) {
165
166
  // Ensure we ses latest value with loadFence
166
- if (!supportsFences()) {
167
+ if (!FULL_FENCE) {
167
168
  // piggybacking on volatile read, simulating loadFence
168
- final ThreadContext oldContext = threadContext;
169
+ final int volatileRead = volatileField;
169
170
  return ((RubyBasicObject) self).instance_variable_get(context, name);
170
171
  } else {
171
172
  UNSAFE.loadFence();
@@ -180,10 +181,10 @@ public class SynchronizationLibrary implements Library {
180
181
  IRubyObject name,
181
182
  IRubyObject value) {
182
183
  // Ensure we make last update visible
183
- if (!supportsFences()) {
184
+ if (!FULL_FENCE) {
184
185
  // piggybacking on volatile write, simulating storeFence
185
186
  final IRubyObject result = ((RubyBasicObject) self).instance_variable_set(name, value);
186
- threadContext = context;
187
+ volatileField = context.getLine();
187
188
  return result;
188
189
  } else {
189
190
  // JRuby uses StampedVariableAccessor which calls fullFence
@@ -21,8 +21,9 @@ module Concurrent
21
21
  # @!macro internal_implementation_note
22
22
  ArrayImplementation = case
23
23
  when Concurrent.on_cruby?
24
- # Because MRI never runs code in parallel, the existing
25
- # non-thread-safe structures should usually work fine.
24
+ # Array is thread-safe in practice because CRuby runs
25
+ # threads one at a time and does not do context
26
+ # switching during the execution of C functions.
26
27
  ::Array
27
28
 
28
29
  when Concurrent.on_jruby?
@@ -63,4 +64,3 @@ module Concurrent
63
64
  end
64
65
 
65
66
  end
66
-
@@ -333,6 +333,13 @@ module Concurrent
333
333
  ivar
334
334
  end
335
335
 
336
+ # Check whether the method is responsive
337
+ #
338
+ # @param [Symbol] method the method being called
339
+ def respond_to_missing?(method, include_private = false)
340
+ @delegate.respond_to?(method) || super
341
+ end
342
+
336
343
  # Perform all enqueued tasks.
337
344
  #
338
345
  # This method must be called from within the executor. It must not be
@@ -383,6 +390,13 @@ module Concurrent
383
390
  ivar.wait
384
391
  ivar
385
392
  end
393
+
394
+ # Check whether the method is responsive
395
+ #
396
+ # @param [Symbol] method the method being called
397
+ def respond_to_missing?(method, include_private = false)
398
+ @delegate.respond_to?(method) || super
399
+ end
386
400
  end
387
401
  private_constant :AwaitDelegator
388
402
 
@@ -79,10 +79,10 @@ module Concurrent
79
79
  # @!method value=(value)
80
80
  # @!macro atomic_fixnum_method_value_set
81
81
  #
82
- # @!method increment(delta)
82
+ # @!method increment(delta = 1)
83
83
  # @!macro atomic_fixnum_method_increment
84
84
  #
85
- # @!method decrement(delta)
85
+ # @!method decrement(delta = 1)
86
86
  # @!macro atomic_fixnum_method_decrement
87
87
  #
88
88
  # @!method compare_and_set(expect, update)
@@ -53,7 +53,7 @@ module Concurrent
53
53
 
54
54
  # @param [Node] head
55
55
  # @return [true, false]
56
- def empty?(head = self.head)
56
+ def empty?(head = head())
57
57
  head.equal? EMPTY
58
58
  end
59
59
 
@@ -48,11 +48,9 @@ module Concurrent
48
48
  def post(delay, *args, &task)
49
49
  raise ArgumentError.new('no block given') unless block_given?
50
50
  return false unless running?
51
- opts = {
52
- executor: @task_executor,
53
- args: args,
54
- timer_set: self
55
- }
51
+ opts = { executor: @task_executor,
52
+ args: args,
53
+ timer_set: self }
56
54
  task = ScheduledTask.execute(delay, opts, &task) # may raise exception
57
55
  task.unscheduled? ? false : task
58
56
  end
@@ -74,11 +72,11 @@ module Concurrent
74
72
  # @param [Hash] opts the options to create the object with.
75
73
  # @!visibility private
76
74
  def ns_initialize(opts)
77
- @queue = Collection::NonConcurrentPriorityQueue.new(order: :min)
78
- @task_executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
79
- @timer_executor = SingleThreadExecutor.new
80
- @condition = Event.new
81
- @ruby_pid = $$ # detects if Ruby has forked
75
+ @queue = Collection::NonConcurrentPriorityQueue.new(order: :min)
76
+ @task_executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
77
+ @timer_executor = SingleThreadExecutor.new
78
+ @condition = Event.new
79
+ @ruby_pid = $$ # detects if Ruby has forked
82
80
  self.auto_terminate = opts.fetch(:auto_terminate, true)
83
81
  end
84
82
 
@@ -90,7 +88,7 @@ module Concurrent
90
88
  #
91
89
  # @!visibility private
92
90
  def post_task(task)
93
- synchronize{ ns_post_task(task) }
91
+ synchronize { ns_post_task(task) }
94
92
  end
95
93
 
96
94
  # @!visibility private
@@ -98,7 +96,7 @@ module Concurrent
98
96
  return false unless ns_running?
99
97
  ns_reset_if_forked
100
98
  if (task.initial_delay) <= 0.01
101
- task.executor.post{ task.process_task }
99
+ task.executor.post { task.process_task }
102
100
  else
103
101
  @queue.push(task)
104
102
  # only post the process method when the queue is empty
@@ -116,7 +114,7 @@ module Concurrent
116
114
  #
117
115
  # @!visibility private
118
116
  def remove_task(task)
119
- synchronize{ @queue.delete(task) }
117
+ synchronize { @queue.delete(task) }
120
118
  end
121
119
 
122
120
  # `ExecutorService` callback called during shutdown.
@@ -148,7 +146,7 @@ module Concurrent
148
146
  task = synchronize { @condition.reset; @queue.peek }
149
147
  break unless task
150
148
 
151
- now = Concurrent.monotonic_time
149
+ now = Concurrent.monotonic_time
152
150
  diff = task.schedule_time - now
153
151
 
154
152
  if diff <= 0
@@ -165,7 +163,7 @@ module Concurrent
165
163
  # queue now must have the same pop time, or a closer one, as
166
164
  # when we peeked).
167
165
  task = synchronize { @queue.pop }
168
- task.executor.post{ task.process_task }
166
+ task.executor.post { task.process_task }
169
167
  else
170
168
  @condition.wait([diff, 60].min)
171
169
  end