concurrent-ruby 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
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