concurrent 0.1 → 0.2.1

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.
data/Rakefile CHANGED
@@ -2,6 +2,8 @@ require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/gempackagetask'
4
4
 
5
+ GEM_VERSION = "0.2.1"
6
+
5
7
  desc "Remove build products"
6
8
  task :clean do
7
9
  rm Dir.glob( "lib/**/*.jar" )
@@ -9,9 +11,10 @@ task :clean do
9
11
  rm Dir.glob( "ext/**/Makefile" )
10
12
  rm Dir.glob( "ext/**/*.{o,#{ Config::CONFIG['DLEXT'] }}" )
11
13
  rm Dir.glob( "java/**/*.{class,jar}" )
12
- rm Dir.glob( "java/concurrent/FuturesService.java" )
13
14
  end
14
15
 
16
+ task :clobber => [ :clean ]
17
+
15
18
  def setup_extension( dir, lib_dir, extension )
16
19
  ext = File.join( "ext", dir )
17
20
  so_name = "#{ extension }.#{ Config::CONFIG['DLEXT'] }"
@@ -47,9 +50,6 @@ end
47
50
 
48
51
  case RUBY_PLATFORM
49
52
  when /java/
50
- file 'java/concurrent/FuturesService.java' => [ 'java/concurrent/FuturesService.java.rb' ] do
51
- sh File.join( ENV['JRUBY_HOME'], 'bin/jruby' ), 'java/concurrent/FuturesService.java.rb', 'java/concurrent/FuturesService.java'
52
- end
53
53
  file 'lib/concurrent/futures.jar' => [ 'java/concurrent/FuturesService.java' ] do
54
54
  Dir.chdir( 'java' ) do
55
55
  sh 'javac', '-classpath', File.join( ENV['JRUBY_HOME'], 'lib/jruby.jar' ), 'concurrent/FuturesService.java'
@@ -67,6 +67,7 @@ task :compile
67
67
 
68
68
  task :test => [ :compile ]
69
69
  Rake::TestTask.new do |task|
70
+ task.ruby_opts << '-rrubygems'
70
71
  task.libs << 'lib'
71
72
  task.libs << 'test'
72
73
  task.test_files = [ "test/test_all.rb" ]
@@ -75,14 +76,15 @@ end
75
76
 
76
77
  gemspec = Gem::Specification.new do |gemspec|
77
78
  gemspec.name = "concurrent"
78
- gemspec.version = "0.1"
79
+ gemspec.version = GEM_VERSION
79
80
  gemspec.author = "MenTaLguY <mental@rydia.net>"
80
81
  gemspec.summary = "Omnibus concurrency library for Ruby"
81
82
  gemspec.test_file = 'test/test_all.rb'
82
83
  gemspec.files = FileList[ 'Rakefile', 'test/*.rb', 'ext/**/*.{c,h,rb}',
83
- 'java/**/*.{java,rb}',
84
+ 'java/**/*.java',
84
85
  "lib/**/*.{rb,jar,#{ Config::CONFIG['DLEXT'] }}" ]
85
86
  gemspec.require_paths = [ 'lib' ]
87
+ gemspec.add_dependency 'scheduler', '>= 0.1'
86
88
 
87
89
  case RUBY_PLATFORM
88
90
  when /java/
@@ -103,6 +103,10 @@ static VALUE wrap_exception(VALUE unused, VALUE ex) {
103
103
  static VALUE rb_thunk_method_missing(int argc, VALUE *argv, VALUE self) {
104
104
  ID name;
105
105
 
106
+ if ( argc < 1 ) {
107
+ rb_raise(rb_eArgError, "Too few arguments (0 for 1)");
108
+ }
109
+
106
110
  name = SYM2ID(argv[0]);
107
111
  self = thunk_ground(self);
108
112
 
@@ -110,8 +114,12 @@ static VALUE rb_thunk_method_missing(int argc, VALUE *argv, VALUE self) {
110
114
  if ( name == inspect_id && argc == 1 ) {
111
115
  Thunk *thunk;
112
116
  Data_Get_Struct(self, Thunk, thunk);
113
- /* FIXME: thunk->source might be nil by the time we get here */
114
- return rb_str_plus(rb_str_plus(rb_str_new2("#<Thunk "), rb_funcall(thunk->source, inspect_id, 0)), rb_str_new2(">"));
117
+ rb_thread_critical = 1;
118
+ VALUE source = thunk->source;
119
+ rb_thread_critical = 0;
120
+ if (RTEST(source)) {
121
+ return rb_str_plus(rb_str_plus(rb_str_new2("#<Thunk "), rb_funcall(source, inspect_id, 0)), rb_str_new2(">"));
122
+ }
115
123
  } else if ( name == respond_to_p_id && argc == 2 ) {
116
124
  if ( ID2SYM(inspect_id) == argv[1] ||
117
125
  ID2SYM(respond_to_p_id) == argv[1] )
@@ -1,8 +1,3 @@
1
- require 'java'
2
-
3
- begin
4
- File.open(ARGV[0], "w") do |stream|
5
- stream.print <<EOS
6
1
  /***** BEGIN LICENSE BLOCK *****
7
2
  * Version: CPL 1.0/GPL 2.0/LGPL 2.1
8
3
  *
@@ -38,6 +33,7 @@ import java.io.IOException;
38
33
  import org.jruby.Ruby;
39
34
  import org.jruby.RubyClass;
40
35
  import org.jruby.RubyException;
36
+ import org.jruby.RubyObject;
41
37
  import org.jruby.exceptions.RaiseException;
42
38
  import org.jruby.runtime.Block;
43
39
  import org.jruby.runtime.CallbackFactory;
@@ -52,15 +48,12 @@ public class FuturesService implements BasicLibraryService {
52
48
  return true;
53
49
  }
54
50
 
55
- public static class Thunk implements IRubyObject {
56
- private Ruby runtime;
57
- private RubyClass klass;
51
+ public static class Thunk extends RubyObject {
58
52
  private IRubyObject source;
59
53
  private IRubyObject value;
60
54
 
61
55
  Thunk(Ruby runtime, RubyClass klass, IRubyObject source) {
62
- this.runtime = runtime;
63
- this.klass = klass;
56
+ super(runtime, klass, false);
64
57
  this.source = source;
65
58
  this.value = null;
66
59
  }
@@ -70,10 +63,13 @@ public class FuturesService implements BasicLibraryService {
70
63
  }
71
64
 
72
65
  public static void setup(final Ruby runtime) throws IOException {
73
- RubyClass cThunk = runtime.getOrCreateModule("Concurrent").defineModuleUnder("Futures").defineClassUnder("Thunk", null, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
66
+ RubyClass cThunk = RubyClass.createBootstrapMetaClass(runtime, null, null, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR, null);
67
+ cThunk.setMetaClass(runtime.getClass("Class"));
68
+ runtime.getOrCreateModule("Concurrent").defineModuleUnder("Futures").setConstant("Thunk", cThunk);
74
69
  CallbackFactory cb = runtime.callbackFactory(Thunk.class);
75
- cThunk.getMetaClass().defineMethod("new", cb.getSingletonMethod("newInstance", IRubyObject.class));
76
- cThunk.defineMethod("value", cb.getSingletonMethod("value", IRubyObject.class));
70
+ cThunk.getSingletonClass().defineMethod("new", cb.getSingletonMethod("newInstance", IRubyObject.class));
71
+ cThunk.getSingletonClass().defineMethod("value", cb.getSingletonMethod("value", IRubyObject.class));
72
+ cThunk.defineMethod("method_missing", cb.getOptMethod("methodMissing"));
77
73
  }
78
74
 
79
75
  public static IRubyObject thunkValue(IRubyObject obj, boolean evaluate) {
@@ -122,42 +118,37 @@ public class FuturesService implements BasicLibraryService {
122
118
  return thunkValue(obj, true);
123
119
  }
124
120
 
125
- /*** Generated wrapper methods ***/
126
- EOS
127
-
128
- def demangle( type )
129
- case type
130
- when /^\[L(.*);$/
131
- "#{ demangle( $1 ) }[]"
132
- else
133
- type
134
- end
135
- end
136
-
137
- Modifier = java.lang.reflect.Modifier
138
-
139
- c = java.lang.Class.for_name("org.jruby.runtime.builtin.IRubyObject")
140
- c.get_declared_methods.each do |method|
141
- parameter_types = method.get_parameter_types.map { |t| demangle( t.to_s ) }
142
- parameter_names = (0...(parameter_types.length)).map { |i| "p#{i}" }
143
- return_type = demangle( method.get_return_type.to_s )
144
- stream.print <<EOS
145
-
146
- #{ Modifier.to_string( method.get_modifiers & ~Modifier::ABSTRACT ) } #{ return_type } #{ method.get_name }(#{ parameter_types.zip( parameter_names ).map { |t, n| "#{t} #{n}" }.join(", ")}) {
147
- #{ return_type == "void" ? "" : "return" } evalThunk(this).#{ method.get_name }(#{ parameter_names.join(", ") });
121
+ public IRubyObject methodMissing(IRubyObject args[], Block block) {
122
+ if (args.length < 1) {
123
+ throw getRuntime().newArgumentError("Too few arguments (0 for 1)");
124
+ }
125
+
126
+ String name = args[0].asSymbol();
127
+ IRubyObject self = thunkValue(this, false);
128
+
129
+ if ( self instanceof Thunk ) {
130
+ if ( name == "inspect" && args.length == 1 ) {
131
+ Thunk thunk = (Thunk)self;
132
+ IRubyObject source;
133
+ synchronized (thunk) {
134
+ source = thunk.source;
135
+ }
136
+ if ( source != null ) {
137
+ Ruby runtime = getRuntime();
138
+ return runtime.newString("#<Thunk " + source.callMethod(runtime.getCurrentContext(), "inspect").asString().toString() + ">");
139
+ }
140
+ } else if ( name == "respond_to?" && args.length == 2 ) {
141
+ if ( args[1].asSymbol() == "inspect" || args[1].asSymbol() == "respond_to?" ) {
142
+ return getRuntime().getTrue();
143
+ }
144
+ }
145
+ }
146
+
147
+ IRubyObject[] args2 = new IRubyObject[args.length-1];
148
+ for ( int i = 0 ; i < args2.length ; i++ ) {
149
+ args2[i] = args[i+1];
150
+ }
151
+ return evalThunk(this).callMethod(getRuntime().getCurrentContext(), name, args2, block);
148
152
  }
149
- EOS
150
- end
151
-
152
- stream.print <<EOS
153
153
  }
154
154
  }
155
- EOS
156
- end
157
- rescue Exception => e
158
- begin
159
- File.unlink(ARGV[0])
160
- rescue Errno::ENOENT
161
- end
162
- raise e
163
- end
@@ -1,2 +1,3 @@
1
1
  require 'concurrent/actors/mailbox'
2
2
  require 'concurrent/actors/actor'
3
+ require 'concurrent/actors/case'
@@ -64,8 +64,8 @@ class Actor
64
64
  end
65
65
  private :current_mailbox
66
66
 
67
- def receive(&prc)
68
- current_mailbox.receive(&prc)
67
+ def receive(timeout=nil, &prc)
68
+ current_mailbox.receive(timeout, &prc)
69
69
  end
70
70
  end
71
71
 
@@ -0,0 +1,45 @@
1
+ # concurrent/actors/case - pattern-matchable Struct
2
+ #
3
+ # Copyright 2007 MenTaLguY <mental@rydia.net>
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # thi slist of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above copyright notice
13
+ # this list of conditions and the following disclaimer in the documentatio
14
+ # and/or other materials provided with the distribution.
15
+ # * Neither the name of the Evan Phoenix nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Concurrent
32
+ module Actors
33
+
34
+ class Case < Struct
35
+ def ===(other)
36
+ return false unless self.class == other.class
37
+ zip(other) do |a, b|
38
+ return false unless a === b
39
+ end
40
+ true
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -36,13 +36,23 @@ rescue LoadError
36
36
  end
37
37
 
38
38
  require 'concurrent/core'
39
+ require 'scheduler'
39
40
 
40
41
  module Concurrent
41
42
  module Actors
42
43
 
44
+ Channel = Core::Channel
45
+
46
+ class TimeoutError < RuntimeError
47
+ end
48
+
49
+ class Actor
43
50
  class Mailbox
51
+ class Timeout < Exception #:nodoc:
52
+ end
53
+
44
54
  def initialize
45
- @channel = Core::Channel.new
55
+ @channel = Channel.new
46
56
  @skipped = []
47
57
  end
48
58
 
@@ -53,39 +63,62 @@ class Mailbox
53
63
  end
54
64
 
55
65
  # safe only for a single reader
56
- def receive
66
+ def receive(timeout=nil)
67
+ filter = Filter.new
57
68
  if block_given?
58
- filter = Filter.new
59
69
  yield filter
70
+ raise ArgumentError, "Empty filter" if filter.empty?
71
+ else
72
+ filter.when(Object) { |m| m }
73
+ end
74
+
75
+ value = nil
76
+ action = nil
77
+
78
+ found_at = nil
79
+ @skipped.each_with_index do |obj, index|
80
+ action = filter.action_for obj
81
+ if action
82
+ value = obj
83
+ found_at = index
84
+ break
85
+ end
86
+ end
87
+ @skipped.delete_at found_at if found_at
60
88
 
61
- value = nil
62
- action = nil
89
+ unless action
90
+ timeout_instance = nil
91
+ timeout_event = nil
63
92
 
64
- found_at = nil
65
- @skipped.each_with_index do |obj, index|
66
- action = filter.action_for obj
67
- if action
68
- value = obj
69
- found_at = index
70
- break
93
+ if timeout
94
+ timeout_instance = Timeout.new
95
+ if timeout > 0
96
+ timeout_event = Scheduler.after_delay!(timeout) do
97
+ @channel << timeout_instance
98
+ end
99
+ else
100
+ @channel << timeout_instance
71
101
  end
72
102
  end
73
- @skipped.delete_at found_at if found_at
74
103
 
75
104
  until action
76
105
  value = @channel.receive
106
+ if Timeout === value
107
+ if value == timeout_instance
108
+ raise TimeoutError, "Receive timed out"
109
+ else
110
+ # Timeout left over from previous call
111
+ next
112
+ end
113
+ end
77
114
  action = filter.action_for value
78
115
  @skipped.push value unless action
79
116
  end
80
117
 
81
- action.call value
82
- else
83
- unless @skipped.empty?
84
- @skipped.shift
85
- else
86
- @channel.receive
87
- end
118
+ timeout_event.cancel if timeout_event
88
119
  end
120
+
121
+ action.call value
89
122
  end
90
123
 
91
124
  class Filter
@@ -102,8 +135,13 @@ class Mailbox
102
135
  pair = @pairs.find { |pattern, action| pattern === value }
103
136
  pair ? pair[1] : nil
104
137
  end
138
+
139
+ def empty?
140
+ @pairs.empty?
141
+ end
105
142
  end
106
143
  end
144
+ end
107
145
 
108
146
  end
109
147
  end
@@ -15,9 +15,10 @@ require 'thwait'
15
15
 
16
16
  module Concurrent
17
17
  module Futures
18
- extend self
19
18
 
20
- Future = self
19
+ module Future
20
+ extend Futures
21
+ end
21
22
 
22
23
  class AsyncError < RuntimeError
23
24
  attr_reader :reason
@@ -33,7 +34,6 @@ require 'concurrent/futures.so' # provides Thunk
33
34
  def async( &block )
34
35
  Thunk.new( Thread.new( &block ) )
35
36
  end
36
- alias new async
37
37
  alias spawn async
38
38
  alias future async
39
39
 
@@ -5,7 +5,40 @@ require 'thread'
5
5
  include Concurrent::Actors
6
6
 
7
7
  class TestActors < Test::Unit::TestCase
8
+ class A ; end
9
+ class B ; end
10
+
8
11
  def test_current
9
12
  assert_instance_of Actor, Actor.current
10
13
  end
14
+
15
+ def test_spawn
16
+ c = Channel.new
17
+ child = Actor.spawn { c << Actor.current }
18
+ assert_equal child, c.receive
19
+ end
20
+
21
+ def test_receive_filter
22
+ c = Channel.new
23
+ child = Actor.spawn do
24
+ Actor.receive do |f|
25
+ f.when( B ) { |m| c << m }
26
+ end
27
+ Actor.receive do |f|
28
+ f.when( A ) { |m| c << m }
29
+ end
30
+ end
31
+ a = A.new
32
+ b = B.new
33
+ child << a
34
+ child << b
35
+ assert_equal b, c.receive
36
+ assert_equal a, c.receive
37
+ end
38
+
39
+ def test_receive_empty_filter
40
+ assert_raise ArgumentError do
41
+ Actor.receive {}
42
+ end
43
+ end
11
44
  end
@@ -47,5 +47,26 @@ class FuturesTests < Test::Unit::TestCase
47
47
  f + 1
48
48
  end
49
49
  end
50
+
51
+ def test_thunk_inspect
52
+ t = Thunk.new Object.new
53
+ t.inspect # should not try to call #value
54
+ end
55
+
56
+ def test_thunk_respond_to?
57
+ t = Thunk.new Object.new
58
+ assert t.respond_to?( :respond_to? )
59
+ assert t.respond_to?( :inspect )
60
+ assert_raise( AsyncError ) do
61
+ t.respond_to?( :arbitrary )
62
+ end
63
+ end
64
+
65
+ def test_thunk_method_missing_short_args
66
+ t = Thunk.new Object.new
67
+ assert_raise( ArgumentError ) do
68
+ t.method_missing
69
+ end
70
+ end
50
71
  end
51
72
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: concurrent
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.1"
7
- date: 2007-05-24 00:00:00 -04:00
6
+ version: 0.2.1
7
+ date: 2007-05-28 00:00:00 -04:00
8
8
  summary: Omnibus concurrency library for Ruby
9
9
  require_paths:
10
10
  - lib
@@ -35,13 +35,14 @@ files:
35
35
  - test/test_parallel.rb
36
36
  - ext/concurrent/futures/futures.c
37
37
  - ext/concurrent/futures/extconf.rb
38
- - java/concurrent/FuturesService.java.rb
38
+ - java/concurrent/FuturesService.java
39
39
  - lib/concurrent/actors.rb
40
40
  - lib/concurrent/futures.rb
41
41
  - lib/concurrent/parallel.rb
42
42
  - lib/concurrent/core.rb
43
43
  - lib/concurrent/actors/actor.rb
44
44
  - lib/concurrent/actors/mailbox.rb
45
+ - lib/concurrent/actors/case.rb
45
46
  test_files:
46
47
  - test/test_all.rb
47
48
  rdoc_options: []
@@ -54,5 +55,13 @@ extensions:
54
55
  - ext/concurrent/futures/extconf.rb
55
56
  requirements: []
56
57
 
57
- dependencies: []
58
-
58
+ dependencies:
59
+ - !ruby/object:Gem::Dependency
60
+ name: scheduler
61
+ version_requirement:
62
+ version_requirements: !ruby/object:Gem::Version::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0.1"
67
+ version: