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 +8 -6
- data/ext/concurrent/futures/futures.c +10 -2
- data/java/concurrent/{FuturesService.java.rb → FuturesService.java} +40 -49
- data/lib/concurrent/actors.rb +1 -0
- data/lib/concurrent/actors/actor.rb +2 -2
- data/lib/concurrent/actors/case.rb +45 -0
- data/lib/concurrent/actors/mailbox.rb +58 -20
- data/lib/concurrent/futures.rb +3 -3
- data/test/test_actors.rb +33 -0
- data/test/test_futures.rb +21 -0
- metadata +14 -5
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 =
|
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/**/*.
|
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
|
-
|
114
|
-
|
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
|
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
|
-
|
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 =
|
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.
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
data/lib/concurrent/actors.rb
CHANGED
@@ -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 =
|
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
|
-
|
62
|
-
|
89
|
+
unless action
|
90
|
+
timeout_instance = nil
|
91
|
+
timeout_event = nil
|
63
92
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
data/lib/concurrent/futures.rb
CHANGED
@@ -15,9 +15,10 @@ require 'thwait'
|
|
15
15
|
|
16
16
|
module Concurrent
|
17
17
|
module Futures
|
18
|
-
extend self
|
19
18
|
|
20
|
-
Future
|
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
|
|
data/test/test_actors.rb
CHANGED
@@ -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
|
data/test/test_futures.rb
CHANGED
@@ -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:
|
7
|
-
date: 2007-05-
|
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
|
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:
|