concurrent 0.1 → 0.2.1

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