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 +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:
|