diff --git a/.travis.yml b/.travis.yml index 9d62e0051..aaf9ecca8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,4 +25,4 @@ matrix: - rvm: jruby-head - rvm: 1.9.3 -script: "bundle exec rake compile && bundle exec rspec --color --backtrace --tag ~unfinished --seed 1 --format documentation ./spec" +script: "rake compile && bundle exec rspec --color --backtrace --tag ~unfinished --seed 1 --format documentation ./spec" diff --git a/Gemfile b/Gemfile index 848592d37..bec4a264a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,11 @@ source 'https://rubygems.org' -gemspec +gemspec name: 'concurrent-ruby' group :development do gem 'rake', '~> 10.3.2' gem 'rake-compiler', '~> 0.9.2' + gem 'gem-compiler', '~> 0.3.0' end group :testing do diff --git a/README.md b/README.md index 73c374264..6c29822ff 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,8 @@ ### Supported Ruby versions -MRI 1.9.3, 2.0, 2.1, JRuby (1.9 mode), and Rubinius 2.x are supported. -Although native code is used for performance optimizations on some platforms, all functionality -is available in pure Ruby. This gem should be fully compatible with any interpreter that is -compliant with Ruby 1.9.3 or newer. +MRI 1.9.3, 2.0, 2.1, 2.2, JRuby (1.9 mode), and Rubinius 2.x are supported. +This gem should be fully compatible with any interpreter that is compliant with Ruby 1.9.3 or newer. ## Features & Documentation @@ -62,6 +60,7 @@ This library contains a variety of concurrency abstractions at high and low leve * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features. * [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ScheduledTask.html): Like a Future scheduled for a specific future time. * [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TimerTask.html): A Thread that periodically wakes up to perform work at regular intervals. +* [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html): Communicating Sequential Processes (CSP). ### Java-inspired ThreadPools and other executors @@ -98,28 +97,45 @@ Lower-level abstractions mainly used as building blocks. * [thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html) * [software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar) -## Installing and Building +## Usage -This gem includes several platform-specific optimizations. To reduce the possibility of -compilation errors, we provide pre-compiled gem packages for several platforms as well -as a pure-Ruby build. Installing the gem should be no different than installing any other -Rubygems-hosted gem. Rubygems will automatically detect your platform and install the -appropriate pre-compiled build. You should never see Rubygems attempt to compile the gem -on installation. Additionally, to ensure compatability with the largest possible number -of Ruby interpreters, the C extensions will *never* load under any Ruby other than MRI, -even when installed. +All abstractions within this gem can be loaded simply by requiring it: -The following gem builds will be built at every release: +```ruby +require 'concurrent' +``` + +To reduce the amount of code loaded at runtime, subsets of this gem can be required: -* concurrent-ruby-x.y.z.gem (pure Ruby) -* concurrent-ruby-x.y.z-java.gem (JRuby) -* concurrent-ruby-x.y.z-x86-linux.gem (Linux 32-bit) -* concurrent-ruby-x.y.z-x86_64-linux.gem (Linux 64-bit) -* concurrent-ruby-x.y.z-x86-mingw32.gem (Windows 32-bit) -* concurrent-ruby-x.y.z-x64-mingw32.gem (Windows 64-bit) -* concurrent-ruby-x.y.z-x86-solaris-2.11.gem (Solaris) +```ruby +require 'concurrent' # everything + +# groups + +require 'concurrent/actor' # Concurrent::Actor and supporting code +require 'concurrent/atomics' # atomic and thread synchronization classes +require 'concurrent/channels' # Concurrent::Channel and supporting code +require 'concurrent/executors' # Thread pools and other executors +require 'concurrent/utilities' # utility methods such as processor count and timers + +# individual abstractions + +require 'concurrent/agent' # Concurrent::Agent +require 'concurrent/async' # Concurrent::Async +require 'concurrent/atomic' # Concurrent::Atomic (formerly the `atomic` gem) +require 'concurrent/dataflow' # Concurrent::dataflow +require 'concurrent/delay' # Concurrent::Delay +require 'concurrent/exchanger' # Concurrent::Exchanger +require 'concurrent/future' # Concurrent::Future +require 'concurrent/ivar' # Concurrent::IVar +require 'concurrent/mvar' # Concurrent::MVar +require 'concurrent/promise' # Concurrent::Promise +require 'concurrent/scheduled_task' # Concurrent::ScheduledTask +require 'concurrent/timer_task' # Concurrent::TimerTask +require 'concurrent/tvar' # Concurrent::TVar +``` -### Installing +## Installation ```shell gem install concurrent-ruby @@ -133,31 +149,64 @@ gem 'concurrent-ruby' and run `bundle install` from your shell. -### Building +### C Extensions for MRI -Because we provide pre-compiled gem builds, users should never need to build the gem manually. -The build process for this gem is completely automated using open source tools. All of -the automation components are available in the [ruby-concurrency/rake-compiler-dev-box](https://github.com/ruby-concurrency/rake-compiler-dev-box) -GitHub repository. +Potential performance improvements may be achieved under MRI by installing optional C extensions. +To minimize installation errors the C extensions are available in the `concurrent-ruby-ext` extension +gem. The extension gem lists `concurrent-ruby` as a dependency so it is not necessary to install both. +Simply install the extension gen: -This gem will compile native C code under MRI and native Java code under JRuby. It is -also possible to build a pure-Ruby version. All builds have identical functionality. -The only difference is performance. Additionally, pure-Ruby classes are always available, -even when using the native optimizations. Please see the [documentation](http://ruby-concurrency.github.io/concurrent-ruby/) -for more details. +```ruby +gem install concurrent-ruby-ext +``` -To build and package the gem using MRI or JRuby, install the necessary build dependencies and run: +or add the following line to Gemfile: -```shell -bundle exec rake compile -bundle exec rake build +```ruby +gem 'concurrent-ruby-ext' +``` + +and run `bundle install` from your shell. + +In code it is only necessary to + +```ruby +require 'concurrent' ``` -To build and package a pure-Ruby gem, on *any* platform and interpreter -(including MRI and JRuby), run: +The `concurrent-ruby` gem will automatically detect the presence of the `concurrent-ruby-ext` gem +and load the appropriate C extensions. -```shell -BUILD_PURE_RUBY='true' bundle exec rake build +#### Note For gem developers + +No gems should depend on `concurrent-ruby-ext`. Doing so will force C extensions on your users. +The best practice is to depend on `concurrent-ruby` and let users to decide if they want C extensions. + +### Building + +All published versions of this gem (core, extension, and several platform-specific packages) are compiled, +packaged, tested, and published using an open, [automated process](https://github.com/ruby-concurrency/rake-compiler-dev-box). +This process can also be used to create pre-compiled binaries of the extension gem for virtally +any platform. *Documentation is forthcoming...* + +``` +*MRI only* +rake build:native # Build concurrent-ruby-ext--.gem into the pkg directory +rake compile:extension # Compile extension + +*JRuby only* +rake build # Build JRuby-specific core gem (alias for `build:core`) +rake build:core # Build concurrent-ruby--java.gem into the pkg directory + +*All except JRuby* +rake build # Build core and extension gems +rake build:core # Build concurrent-ruby-.gem into the pkg directory +rake build:ext # Build concurrent-ruby-ext-.gem into the pkg directory + +*All* +rake clean # Remove any temporary products +rake clobber # Remove any generated file +rake compile # Compile all the extensions ``` ## Maintainers @@ -167,6 +216,7 @@ BUILD_PURE_RUBY='true' bundle exec rake build * [Chris Seaton](https://github.com/chrisseaton) * [Lucas Allan](https://github.com/lucasallan) * [Petr Chalupa](https://github.com/pitr-ch) +* [Paweł Obrok](https://github.com/obrok) ### Contributing diff --git a/Rakefile b/Rakefile index 094554f72..5da44fcbe 100644 --- a/Rakefile +++ b/Rakefile @@ -1,20 +1,32 @@ -require 'bundler/gem_tasks' -require 'rake/extensiontask' -require 'rake/javaextensiontask' +#!/usr/bin/env rake -GEMSPEC = Gem::Specification.load('concurrent-ruby.gemspec') -EXTENSION_NAME = 'concurrent_ruby_ext' +require_relative './lib/extension_helper' -Bundler::GemHelper.install_tasks +## load the two gemspec files +CORE_GEMSPEC = Gem::Specification.load('concurrent-ruby.gemspec') +EXT_GEMSPEC = Gem::Specification.load('concurrent-ruby-ext.gemspec') -$:.push File.join(File.dirname(__FILE__), 'lib') -require 'extension_helper' +## constants used for compile/build tasks + +GEM_NAME = 'concurrent-ruby' +EXTENSION_NAME = 'extension' +JAVA_EXT_NAME = 'concurrent_ruby_ext' + +if Concurrent.jruby? + CORE_GEM = "#{GEM_NAME}-#{Concurrent::VERSION}-java.gem" +else + CORE_GEM = "#{GEM_NAME}-#{Concurrent::VERSION}.gem" + EXTENSION_GEM = "#{GEM_NAME}-ext-#{Concurrent::VERSION}.gem" + NATIVE_GEM = "#{GEM_NAME}-ext-#{Concurrent::VERSION}-#{Gem::Platform.new(RUBY_PLATFORM)}.gem" +end + +## safely load all the rake tasks in the `tasks` directory def safe_load(file) begin load file rescue LoadError => ex - puts 'Error loading rake tasks, but will continue...' + puts "Error loading rake tasks from '#{file}' but will continue..." puts ex.message end end @@ -23,21 +35,24 @@ Dir.glob('tasks/**/*.rake').each do |rakefile| safe_load rakefile end -desc 'Run benchmarks' -task :bench do - exec 'ruby -Ilib -Iext examples/bench_atomic.rb' -end +if Concurrent.jruby? -if defined?(JRUBY_VERSION) + ## create the compile task for the JRuby-specific gem + require 'rake/javaextensiontask' - Rake::JavaExtensionTask.new(EXTENSION_NAME, GEMSPEC) do |ext| + Rake::JavaExtensionTask.new(JAVA_EXT_NAME, CORE_GEMSPEC) do |ext| ext.ext_dir = 'ext' end elsif Concurrent.allow_c_extensions? - Rake::ExtensionTask.new(EXTENSION_NAME, GEMSPEC) do |ext| - ext.ext_dir = "ext/#{EXTENSION_NAME}" + ## create the compile tasks for the extension gem + require 'rake/extensiontask' + + Rake::ExtensionTask.new(EXTENSION_NAME, EXT_GEMSPEC) do |ext| + ext.ext_dir = 'ext/concurrent' + ext.lib_dir = 'lib/concurrent' + ext.source_pattern = '*.{c,h}' ext.cross_compile = true ext.cross_platform = ['x86-mingw32', 'x64-mingw32'] end @@ -59,20 +74,61 @@ elsif Concurrent.allow_c_extensions? end end else - task :clean + ## create an empty compile task task :compile - task "compile:#{EXTENSION_NAME}" end -Rake::Task[:clean].enhance do +task :clean do rm_rf 'pkg/classes' rm_rf 'tmp' rm_rf 'lib/1.9' rm_rf 'lib/2.0' - rm_f Dir.glob('./lib/*.jar') + rm_f Dir.glob('./**/*.so') rm_f Dir.glob('./**/*.bundle') + rm_f Dir.glob('./lib/*.jar') + mkdir_p 'pkg' end +## create build tasks tailored to current platform + +namespace :build do + + build_deps = [:clean] + build_deps << :compile if Concurrent.jruby? + + desc "Build #{CORE_GEM} into the pkg directory" + task :core => build_deps do + sh "gem build #{CORE_GEMSPEC.name}.gemspec" + sh 'mv *.gem pkg/' + end + + unless Concurrent.jruby? + desc "Build #{EXTENSION_GEM} into the pkg directory" + task :ext => [:clean] do + sh "gem build #{EXT_GEMSPEC.name}.gemspec" + sh 'mv *.gem pkg/' + end + end + + if Concurrent.allow_c_extensions? + desc "Build #{NATIVE_GEM} into the pkg directory" + task :native do + sh "gem compile pkg/#{EXTENSION_GEM}" + sh 'mv *.gem pkg/' + end + end +end + +if Concurrent.jruby? + desc 'Build JRuby-specific core gem (alias for `build:core`)' + task :build => ['build:core'] +else + desc 'Build core and extension gems' + task :build => ['build:core', 'build:ext'] +end + +## the RSpec task that compiles extensions when available + begin require 'rspec' require 'rspec/core/rake_task' diff --git a/build-tests/atomic_boolean_builds_spec.rb b/build-tests/atomic_boolean_builds_spec.rb new file mode 100644 index 000000000..9d754fba4 --- /dev/null +++ b/build-tests/atomic_boolean_builds_spec.rb @@ -0,0 +1,84 @@ +require 'benchmark' +require_relative 'example_group_extensions' +require_relative 'platform_helpers' + +require 'concurrent/atomics' + +def atomic_boolean_test(clazz, opts = {}) + threads = opts.fetch(:threads, 5) + tests = opts.fetch(:tests, 100) + + atomic = Concurrent.const_get(clazz.to_s).new + latch = Concurrent::CountDownLatch.new(threads) + + stats = Benchmark.measure do + threads.times do |i| + Thread.new do + tests.times{ atomic.value = true } + latch.count_down + end + end + latch.wait + end + stats +end + +describe Concurrent::AtomicBoolean do + + let!(:threads) { 10 } + let!(:tests) { 1000 } + + describe Concurrent::MutexAtomicBoolean do + + specify 'is defined' do + expect(defined?(Concurrent::MutexAtomicBoolean)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_boolean_test('MutexAtomicBoolean', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + if jruby? + + describe Concurrent::JavaAtomicBoolean do + + specify 'Concurrent::JavaAtomicBoolean is defined' do + expect(defined?(Concurrent::JavaAtomicBoolean)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_boolean_test('JavaAtomicBoolean', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + else + + specify 'Concurrent::JavaAtomicBoolean is not defined' do + expect(defined?(Concurrent::JavaAtomicBoolean)).to be_falsey + end + end + + if 'EXT' == ENV['TEST_PLATFORM'].strip + + describe Concurrent::CAtomicBoolean do + + specify 'Concurrent::CAtomicBoolean is defined' do + expect(defined?(Concurrent::CAtomicBoolean)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_boolean_test('CAtomicBoolean', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + else + + specify 'Concurrent::CAtomicBoolean is not defined' do + expect(defined?(Concurrent::CAtomicBoolean)).to be_falsey + end + end +end diff --git a/build-tests/atomic_fixnum_builds_spec.rb b/build-tests/atomic_fixnum_builds_spec.rb new file mode 100644 index 000000000..d35071637 --- /dev/null +++ b/build-tests/atomic_fixnum_builds_spec.rb @@ -0,0 +1,84 @@ +require 'benchmark' +require_relative 'example_group_extensions' +require_relative 'platform_helpers' + +require 'concurrent/atomics' + +def atomic_fixnum_test(clazz, opts = {}) + threads = opts.fetch(:threads, 5) + tests = opts.fetch(:tests, 100) + + atomic = Concurrent.const_get(clazz.to_s).new + latch = Concurrent::CountDownLatch.new(threads) + + stats = Benchmark.measure do + threads.times do |i| + Thread.new do + tests.times{ atomic.up } + latch.count_down + end + end + latch.wait + end + stats +end + +describe Concurrent::AtomicFixnum do + + let!(:threads) { 10 } + let!(:tests) { 1000 } + + describe Concurrent::MutexAtomicFixnum do + + specify 'is defined' do + expect(defined?(Concurrent::MutexAtomicFixnum)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_fixnum_test('MutexAtomicFixnum', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + if jruby? + + describe Concurrent::JavaAtomicFixnum do + + specify 'Concurrent::JavaAtomicFixnum is defined' do + expect(defined?(Concurrent::JavaAtomicFixnum)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_fixnum_test('JavaAtomicFixnum', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + else + + specify 'Concurrent::JavaAtomicFixnum is not defined' do + expect(defined?(Concurrent::JavaAtomicFixnum)).to be_falsey + end + end + + if 'EXT' == ENV['TEST_PLATFORM'].strip + + describe Concurrent::CAtomicFixnum do + + specify 'Concurrent::CAtomicFixnum is defined' do + expect(defined?(Concurrent::CAtomicFixnum)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_fixnum_test('CAtomicFixnum', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + else + + specify 'Concurrent::CAtomicFixnum is not defined' do + expect(defined?(Concurrent::CAtomicFixnum)).to be_falsey + end + end +end diff --git a/build-tests/atomic_reference_builds_spec.rb b/build-tests/atomic_reference_builds_spec.rb new file mode 100644 index 000000000..846a20ba8 --- /dev/null +++ b/build-tests/atomic_reference_builds_spec.rb @@ -0,0 +1,84 @@ +require 'benchmark' +require_relative 'example_group_extensions' +require_relative 'platform_helpers' + +require 'concurrent/atomics' + +def atomic_reference_test(clazz, opts = {}) + threads = opts.fetch(:threads, 5) + tests = opts.fetch(:tests, 100) + + atomic = Concurrent.const_get(clazz.to_s).new + latch = Concurrent::CountDownLatch.new(threads) + + stats = Benchmark.measure do + threads.times do |i| + Thread.new do + tests.times{ atomic.value = true } + latch.count_down + end + end + latch.wait + end + stats +end + +describe Concurrent::Atomic do + + let!(:threads) { 10 } + let!(:tests) { 1000 } + + describe Concurrent::MutexAtomic do + + specify 'is defined' do + expect(defined?(Concurrent::MutexAtomic)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_reference_test('MutexAtomic', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + if jruby? && 'JRUBY' == ENV['TEST_PLATFORM'].strip + + describe Concurrent::JavaAtomic do + + specify 'Concurrent::JavaAtomic is defined' do + expect(defined?(Concurrent::JavaAtomic)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_reference_test('JavaAtomic', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + else + + specify 'Concurrent::JavaAtomic is not defined' do + expect(defined?(Concurrent::JavaAtomic)).to be_falsey + end + end + + if 'EXT' == ENV['TEST_PLATFORM'].strip + + describe Concurrent::CAtomic do + + specify 'Concurrent::CAtomic is defined' do + expect(defined?(Concurrent::CAtomic)).to be_truthy + end + + specify 'runs the benchmarks' do + stats = atomic_reference_test('CAtomic', threads: threads, tests: tests) + expect(stats).to be_benchmark_results + end + end + + else + + specify 'Concurrent::CAtomic is not defined' do + expect(defined?(Concurrent::CAtomic)).to be_falsey + end + end +end diff --git a/build-tests/example_group_extensions.rb b/build-tests/example_group_extensions.rb new file mode 100644 index 000000000..f2dd88497 --- /dev/null +++ b/build-tests/example_group_extensions.rb @@ -0,0 +1,7 @@ +require 'benchmark' + +RSpec::Matchers.define :be_benchmark_results do + match do |actual| + actual.is_a? Benchmark::Tms + end +end diff --git a/build-tests/platform_helpers.rb b/build-tests/platform_helpers.rb new file mode 100644 index 000000000..6067f1843 --- /dev/null +++ b/build-tests/platform_helpers.rb @@ -0,0 +1,18 @@ +require 'rbconfig' + +def windows? + host_os = RbConfig::CONFIG['host_os'] + host_os =~ /mswin32/i || host_os =~ /mingw32/i +end + +def mri?(engine = RUBY_ENGINE) + engine == 'ruby' +end + +def jruby?(engine = RUBY_ENGINE) + engine == 'jruby' +end + +def rbx?(engine = RUBY_ENGINE) + engine == 'rbx' +end diff --git a/build-tests/runner.rb b/build-tests/runner.rb new file mode 100755 index 000000000..4b7b2171b --- /dev/null +++ b/build-tests/runner.rb @@ -0,0 +1,117 @@ +#!/usr/bin/env ruby + +if File.exist?('Gemfile') + puts <<-WARNING +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!! A Gemfile has been detected. This will likely cause some tests !! +!! to erroneously fail (RSpec + Bundler shenanigans!). You may need !! +!! to run tests from a different directory. !! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + WARNING +end + +$:.push File.join(File.dirname(__FILE__), '..', 'lib') + +require 'concurrent/version' +require_relative 'platform_helpers' + + +EXT_PLATFORMS = { + 'i686-linux' => 'x86-linux', + 'x86_64-linux' => 'x86_64-linux', + 'i386-mingw32' => 'x86-mingw32', + 'x64-mingw32' => 'x64-mingw32', + 'i386-solaris2.11' => 'x86-solaris-2.11', + 'x86_64-darwin14.0' => 'x86_64-darwin-14', +} + +TEST_PATH = File.dirname(__FILE__) +PKG_PATH = File.join(File.dirname(__FILE__), '..', 'pkg') +TEST_FILES = Dir["#{TEST_PATH}/*_spec.rb"] + +RSPEC = "rspec --default-path #{TEST_PATH} -fd #{windows? ? '' : '--color'} --seed 0" + +INSTALL_RSPEC_COMMAND = 'gem install rspec' + +UNINSTALL_GEMS_COMMAND = <<-CMD +gem uninstall -q -a -I concurrent-ruby-ext && \ +gem uninstall -q -a -I concurrent-ruby && \ +gem uninstall -q -a -I ref +CMD + +PLATFORM_BREAK = "######################################################################\n" +SUITE_BREAK = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + +def platform_specific_extensions?(platform = RUBY_PLATFORM) + EXT_PLATFORMS.keys.include?(platform) && + File.exists?("#{PKG_PATH}/#{extension_gem_name(platform)}") +end + +def extension_gem_name(platform = RUBY_PLATFORM) + platform = EXT_PLATFORMS.fetch(platform, '') + platform = '-' + platform unless platform.empty? + "concurrent-ruby-ext-#{Concurrent::VERSION}#{platform}.gem" +end + +def install_gems_command(ext, platform = '') + cmd = "gem install #{PKG_PATH}/concurrent-ruby-#{Concurrent::VERSION}.gem" + if ext + cmd << " && gem install #{PKG_PATH}/#{extension_gem_name(platform)}" + end + cmd +end + +def install_java_gem_command + "gem install #{PKG_PATH}/concurrent-ruby-#{Concurrent::VERSION}-java.gem" +end + +def run_test_suite(files, ext, platform = '') + + test_platform = if ext + 'EXT' + elsif jruby?(platform) + 'JRUBY' + else + 'RUBY' + end + + cmd = if jruby?(platform) + install_java_gem_command + else + install_gems_command(ext, platform) + end + ok = system(cmd) + + files.each do |file| + if windows? + cmd = "set TEST_PLATFORM=#{test_platform} && #{RSPEC} #{file}" + else + cmd = "TEST_PLATFORM='#{test_platform}' #{RSPEC} #{file}" + end + ok = system(cmd) + end + + ok = system(UNINSTALL_GEMS_COMMAND) +end + +ok = system(INSTALL_RSPEC_COMMAND) +ok = system(UNINSTALL_GEMS_COMMAND) + +puts PLATFORM_BREAK +puts RUBY_PLATFORM +puts SUITE_BREAK + +run_test_suite(TEST_FILES, false) +if jruby? + puts SUITE_BREAK + run_test_suite(TEST_FILES, false, 'jruby') +elsif mri? + if ! windows? + puts SUITE_BREAK + run_test_suite(TEST_FILES, true) + end + if platform_specific_extensions?(RUBY_PLATFORM) + puts SUITE_BREAK + run_test_suite(TEST_FILES, true, RUBY_PLATFORM) + end +end diff --git a/concurrent-ruby-ext.gemspec b/concurrent-ruby-ext.gemspec new file mode 100644 index 000000000..c0e6040c0 --- /dev/null +++ b/concurrent-ruby-ext.gemspec @@ -0,0 +1,35 @@ +$:.push File.join(File.dirname(__FILE__), 'lib') + +require 'concurrent/version' + +Gem::Specification.new do |s| + s.name = 'concurrent-ruby-ext' + s.version = Concurrent::VERSION + s.platform = Gem::Platform::RUBY + s.author = "Jerry D'Antonio" + s.email = 'jerry.dantonio@gmail.com' + s.homepage = 'http://www.concurrent-ruby.com' + s.summary = 'C extensions to optimize concurrent-ruby under MRI.' + s.license = 'MIT' + s.date = Time.now.strftime('%Y-%m-%d') + + s.description = <<-EOF + C extensions to optimize the concurrent-ruby gem when running under MRI. + Please see http://concurrent-ruby.com for more information. + EOF + + s.files = Dir['ext/**/*.{h,c,cpp}'] + s.files += [ + 'lib/extension_helper.rb', + 'lib/concurrent/atomic_reference/concurrent_update_error.rb', + 'lib/concurrent/atomic_reference/direct_update.rb', + 'lib/concurrent/atomic_reference/numeric_cas_wrapper.rb', + ] + s.extra_rdoc_files = Dir['README*', 'LICENSE*', 'CHANGELOG*'] + s.require_paths = ['lib', 'ext'] + s.extensions = 'ext/concurrent/extconf.rb' + + s.required_ruby_version = '>= 1.9.3' + + s.add_runtime_dependency 'concurrent-ruby', "~> #{Concurrent::VERSION}" +end diff --git a/concurrent-ruby.gemspec b/concurrent-ruby.gemspec index 63f7142f3..d4e306560 100644 --- a/concurrent-ruby.gemspec +++ b/concurrent-ruby.gemspec @@ -18,21 +18,16 @@ Gem::Specification.new do |s| Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns. EOF - s.files = Dir['lib/**/*'] + s.files = Dir['lib/**/*.rb'] s.extra_rdoc_files = Dir['README*', 'LICENSE*', 'CHANGELOG*'] s.require_paths = ['lib'] if defined?(JRUBY_VERSION) - s.files += Dir['lib/concurrent_ruby_ext.jar'] + s.files += Dir['lib/**/*.jar'] s.platform = 'java' - elsif ! ENV['BUILD_PURE_RUBY'] - s.extensions = 'ext/concurrent_ruby_ext/extconf.rb' - s.files += Dir['ext/**/*.{h,c,cpp}'] + else + s.add_runtime_dependency 'ref', '~> 1.0', '>= 1.0.5' end s.required_ruby_version = '>= 1.9.3' - - unless defined?(JRUBY_VERSION) - s.add_dependency 'ref', '~> 1.0.5' - end end diff --git a/examples/atomic_example.rb b/examples/atomic_example.rb index 5debe298f..0c97b2c24 100644 --- a/examples/atomic_example.rb +++ b/examples/atomic_example.rb @@ -1,4 +1,4 @@ -require 'concurrent' +require 'concurrent/atomic' my_atomic = Concurrent::Atomic.new(0) my_atomic.update {|v| v + 1} diff --git a/examples/bench_atomic.rb b/examples/bench_atomic.rb old mode 100644 new mode 100755 index f4b164eb0..9f466b0ea --- a/examples/bench_atomic.rb +++ b/examples/bench_atomic.rb @@ -1,7 +1,17 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../lib', __FILE__) + require 'benchmark' require 'rbconfig' require 'thread' -require 'concurrent' + +require 'concurrent/atomic' + +if RUBY_PLATFORM != 'java' && ! defined? Concurrent::CAtomic + warn "[WARN] C extensions not loaded!" +end + Thread.abort_on_exception = true $go = false # for synchronizing parallel threads diff --git a/examples/bench_atomic_1.rb b/examples/bench_atomic_1.rb old mode 100644 new mode 100755 index c945e75ea..711fa3ed8 --- a/examples/bench_atomic_1.rb +++ b/examples/bench_atomic_1.rb @@ -6,7 +6,12 @@ require 'thread' require 'benchmark' -require 'concurrent' +require 'concurrent/atomic' + +unless defined? Concurrent::CAtomic + warn "[ERROR] C extensions not loaded!" + exit(1) +end Thread.abort_on_exception = true diff --git a/examples/benchmark_atomic_boolean.rb b/examples/benchmark_atomic_boolean.rb index 6f6fda918..29e17069f 100755 --- a/examples/benchmark_atomic_boolean.rb +++ b/examples/benchmark_atomic_boolean.rb @@ -2,7 +2,7 @@ $:.push File.join(File.dirname(__FILE__), '../lib') -require 'concurrent' +require 'concurrent/atomics' require 'benchmark' require 'rbconfig' diff --git a/examples/benchmark_atomic_fixnum.rb b/examples/benchmark_atomic_fixnum.rb index f1c5c081f..f096c6d2a 100755 --- a/examples/benchmark_atomic_fixnum.rb +++ b/examples/benchmark_atomic_fixnum.rb @@ -2,7 +2,7 @@ $:.push File.join(File.dirname(__FILE__), '../lib') -require 'concurrent' +require 'concurrent/atomics' require 'benchmark' require 'rbconfig' diff --git a/ext/concurrent_ruby_ext/atomic_boolean.c b/ext/concurrent/atomic_boolean.c similarity index 100% rename from ext/concurrent_ruby_ext/atomic_boolean.c rename to ext/concurrent/atomic_boolean.c diff --git a/ext/concurrent_ruby_ext/atomic_boolean.h b/ext/concurrent/atomic_boolean.h similarity index 100% rename from ext/concurrent_ruby_ext/atomic_boolean.h rename to ext/concurrent/atomic_boolean.h diff --git a/ext/concurrent_ruby_ext/atomic_fixnum.c b/ext/concurrent/atomic_fixnum.c similarity index 100% rename from ext/concurrent_ruby_ext/atomic_fixnum.c rename to ext/concurrent/atomic_fixnum.c diff --git a/ext/concurrent_ruby_ext/atomic_fixnum.h b/ext/concurrent/atomic_fixnum.h similarity index 100% rename from ext/concurrent_ruby_ext/atomic_fixnum.h rename to ext/concurrent/atomic_fixnum.h diff --git a/ext/concurrent_ruby_ext/atomic_reference.c b/ext/concurrent/atomic_reference.c similarity index 100% rename from ext/concurrent_ruby_ext/atomic_reference.c rename to ext/concurrent/atomic_reference.c diff --git a/ext/concurrent_ruby_ext/atomic_reference.h b/ext/concurrent/atomic_reference.h similarity index 100% rename from ext/concurrent_ruby_ext/atomic_reference.h rename to ext/concurrent/atomic_reference.h diff --git a/ext/concurrent_ruby_ext/extconf.rb b/ext/concurrent/extconf.rb similarity index 87% rename from ext/concurrent_ruby_ext/extconf.rb rename to ext/concurrent/extconf.rb index eb635c2f9..76cb706dd 100644 --- a/ext/concurrent_ruby_ext/extconf.rb +++ b/ext/concurrent/extconf.rb @@ -1,9 +1,8 @@ require 'fileutils' -$:.push File.join(File.dirname(__FILE__), '../../lib') -require 'extension_helper' +require_relative '../../lib/extension_helper' -EXTENSION_NAME = 'concurrent_ruby_ext' +EXTENSION_NAME = 'extension' def create_dummy_makefile File.open('Makefile', 'w') do |f| @@ -50,7 +49,7 @@ def compiler_is_gcc } CODE - create_makefile(EXTENSION_NAME) + create_makefile('concurrent/' + EXTENSION_NAME) rescue create_dummy_makefile warn 'C optimizations cannot be compiled on this version of Ruby.' diff --git a/ext/concurrent_ruby_ext/rb_concurrent.c b/ext/concurrent/rb_concurrent.c similarity index 97% rename from ext/concurrent_ruby_ext/rb_concurrent.c rename to ext/concurrent/rb_concurrent.c index c8d85ef34..e927ab4f8 100644 --- a/ext/concurrent_ruby_ext/rb_concurrent.c +++ b/ext/concurrent/rb_concurrent.c @@ -11,9 +11,9 @@ static VALUE rb_cAtomic; static VALUE rb_cAtomicBoolean; static VALUE rb_cAtomicFixnum; -// Init_concurrent_ruby_ext +// Init_extension -void Init_concurrent_ruby_ext() { +void Init_extension() { // define modules and classes rb_mConcurrent = rb_define_module("Concurrent"); diff --git a/ext/concurrent_ruby_ext/ruby_193_compatible.h b/ext/concurrent/ruby_193_compatible.h similarity index 100% rename from ext/concurrent_ruby_ext/ruby_193_compatible.h rename to ext/concurrent/ruby_193_compatible.h diff --git a/lib/concurrent/atomic.rb b/lib/concurrent/atomic.rb index ab65c2bae..d6c2bd0bc 100644 --- a/lib/concurrent/atomic.rb +++ b/lib/concurrent/atomic.rb @@ -12,6 +12,7 @@ end ##################################################################### +require_relative '../extension_helper' require 'concurrent/atomic_reference/concurrent_update_error' require 'concurrent/atomic_reference/mutex_atomic' @@ -77,7 +78,7 @@ class Concurrent::Atomic < Concurrent::JavaAtomic class Concurrent::Atomic < Concurrent::RbxAtomic end -elsif Concurrent.allow_c_native_class?('CAtomic') +elsif defined? Concurrent::CAtomic # @!macro atomic_reference class Concurrent::Atomic < Concurrent::CAtomic diff --git a/lib/concurrent/atomic/atomic_boolean.rb b/lib/concurrent/atomic/atomic_boolean.rb index 2e88b2414..7bcdfe6e4 100644 --- a/lib/concurrent/atomic/atomic_boolean.rb +++ b/lib/concurrent/atomic/atomic_boolean.rb @@ -1,5 +1,4 @@ require_relative '../../extension_helper' -Concurrent.safe_require_c_extensions module Concurrent @@ -162,7 +161,7 @@ def make_false class AtomicBoolean < JavaAtomicBoolean end - elsif Concurrent.allow_c_native_class?('CAtomicBoolean') + elsif defined?(CAtomicBoolean) # @!macro atomic_boolean class CAtomicBoolean diff --git a/lib/concurrent/atomic/atomic_fixnum.rb b/lib/concurrent/atomic/atomic_fixnum.rb index 31863112f..87e474e8e 100644 --- a/lib/concurrent/atomic/atomic_fixnum.rb +++ b/lib/concurrent/atomic/atomic_fixnum.rb @@ -1,5 +1,4 @@ require_relative '../../extension_helper' -Concurrent.safe_require_c_extensions module Concurrent @@ -166,7 +165,7 @@ def compare_and_set(expect, update) class AtomicFixnum < JavaAtomicFixnum end - elsif Concurrent.allow_c_native_class?('CAtomicFixnum') + elsif defined?(CAtomicFixnum) # @!macro atomic_fixnum class CAtomicFixnum diff --git a/lib/concurrent/atomic_reference/jruby.rb b/lib/concurrent/atomic_reference/jruby.rb index 7533aec01..adb7e23e4 100644 --- a/lib/concurrent/atomic_reference/jruby.rb +++ b/lib/concurrent/atomic_reference/jruby.rb @@ -1,5 +1,4 @@ require_relative '../../extension_helper' -Concurrent.safe_require_java_extensions if defined?(Concurrent::JavaAtomic) require 'concurrent/atomic_reference/direct_update' diff --git a/lib/concurrent/atomic_reference/ruby.rb b/lib/concurrent/atomic_reference/ruby.rb index 437e1d3e4..6f9b76631 100644 --- a/lib/concurrent/atomic_reference/ruby.rb +++ b/lib/concurrent/atomic_reference/ruby.rb @@ -1,37 +1,29 @@ -require_relative '../../extension_helper' +if defined? Concurrent::CAtomic + require_relative '../../extension_helper' + require 'concurrent/atomic_reference/direct_update' + require 'concurrent/atomic_reference/numeric_cas_wrapper' -if Concurrent.allow_c_extensions? - begin - require 'concurrent_ruby_ext' - rescue LoadError - # may be a Windows cross-compiled native gem - require "#{RUBY_VERSION[0..2]}/concurrent_ruby_ext" - end -end - -require 'concurrent/atomic_reference/direct_update' -require 'concurrent/atomic_reference/numeric_cas_wrapper' - -module Concurrent + module Concurrent - # @!macro atomic_reference - class CAtomic - include Concurrent::AtomicDirectUpdate - include Concurrent::AtomicNumericCompareAndSetWrapper + # @!macro atomic_reference + class CAtomic + include Concurrent::AtomicDirectUpdate + include Concurrent::AtomicNumericCompareAndSetWrapper - # @!method initialize - # @!macro atomic_reference_method_initialize + # @!method initialize + # @!macro atomic_reference_method_initialize - # @!method get - # @!macro atomic_reference_method_get + # @!method get + # @!macro atomic_reference_method_get - # @!method set - # @!macro atomic_reference_method_set + # @!method set + # @!macro atomic_reference_method_set - # @!method get_and_set - # @!macro atomic_reference_method_get_and_set + # @!method get_and_set + # @!macro atomic_reference_method_get_and_set - # @!method _compare_and_set - # @!macro atomic_reference_method_compare_and_set + # @!method _compare_and_set + # @!macro atomic_reference_method_compare_and_set + end end end diff --git a/lib/concurrent/version.rb b/lib/concurrent/version.rb index f0e3f6e3c..387fd018e 100644 --- a/lib/concurrent/version.rb +++ b/lib/concurrent/version.rb @@ -1,3 +1,3 @@ module Concurrent - VERSION = '0.7.2' + VERSION = '0.8.0.pre3' end diff --git a/lib/extension_helper.rb b/lib/extension_helper.rb index a342b598f..8ddf1bb1b 100644 --- a/lib/extension_helper.rb +++ b/lib/extension_helper.rb @@ -1,28 +1,37 @@ module Concurrent + @@c_ext_loaded ||= false + @@java_ext_loaded ||= false + # @!visibility private def self.allow_c_extensions? defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' end # @!visibility private - def self.allow_c_native_class?(clazz) - allow_c_extensions? && Concurrent.const_defined?(clazz) - rescue - false + def self.jruby? + RUBY_PLATFORM == 'java' end - # @!visibility private - def self.safe_require_c_extensions - require 'concurrent_ruby_ext' if allow_c_extensions? - rescue LoadError - #warn 'Attempted to load C extensions on unsupported platform. Continuing with pure-Ruby.' - end - - # @!visibility private - def self.safe_require_java_extensions - require 'concurrent_ruby_ext' if RUBY_PLATFORM == 'java' - rescue LoadError - #warn 'Attempted to load Java extensions on unsupported platform. Continuing with pure-Ruby.' + if allow_c_extensions? && !@@c_ext_loaded + begin + require 'concurrent/extension' + @@c_ext_loaded = true + rescue LoadError + # may be a Windows cross-compiled native gem + begin + require "concurrent/#{RUBY_VERSION[0..2]}/extension" + @@c_ext_loaded = true + rescue LoadError + warn 'Performance on MRI may be improved with the concurrent-ruby-ext gem. Please see http://concurrent-ruby.com' + end + end + elsif jruby? && !@@java_ext_loaded + begin + require 'concurrent_ruby_ext' + @@java_ext_loaded = true + rescue LoadError + warn 'Performance on JRuby may be improved by installing the pre-compiled Java extensions. Please see http://concurrent-ruby.com' + end end end diff --git a/spec/concurrent/atomic_spec.rb b/spec/concurrent/atomic_spec.rb index f7343a9b9..4d9912a51 100644 --- a/spec/concurrent/atomic_spec.rb +++ b/spec/concurrent/atomic_spec.rb @@ -154,19 +154,19 @@ module Concurrent describe Atomic do if TestHelpers.jruby? it 'inherits from JavaAtomic' do - expect(Atomic.ancestors).to include(JavaAtomic) + expect(Atomic.ancestors).to include(Concurrent::JavaAtomic) end elsif TestHelpers.use_c_extensions? it 'inherits from CAtomic' do - expect(Atomic.ancestors).to include(CAtomic) + expect(Atomic.ancestors).to include(Concurrent::CAtomic) end elsif TestHelpers.rbx? it 'inherits from RbxAtomic' do - expect(Atomic.ancestors).to include(RbxAtomic) + expect(Atomic.ancestors).to include(Concurrent::RbxAtomic) end else it 'inherits from MutexAtomic' do - expect(Atomic.ancestors).to include(MutexAtomic) + expect(Atomic.ancestors).to include(Concurrent::MutexAtomic) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index faaae3992..439ce5830 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,11 +8,15 @@ SimpleCov.start do project_name 'concurrent-ruby' + add_filter '/build-tests/' add_filter '/coverage/' add_filter '/doc/' + add_filter '/examples/' add_filter '/pkg/' add_filter '/spec/' add_filter '/tasks/' + add_filter '/yard-template/' + add_filter '/yardoc/' end require 'concurrent' diff --git a/yardoc b/yardoc new file mode 160000 index 000000000..db7483b80 --- /dev/null +++ b/yardoc @@ -0,0 +1 @@ +Subproject commit db7483b80f6efb7df9930f0031508d7e2eb6bad0