concurrent 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/gempackagetask'
4
+
5
+ desc "Remove build products"
6
+ task :clean do
7
+ rm Dir.glob( "lib/**/*.jar" )
8
+ rm Dir.glob( "lib/**/*.#{ Config::CONFIG['DLEXT'] }" )
9
+ rm Dir.glob( "ext/**/Makefile" )
10
+ rm Dir.glob( "ext/**/*.{o,#{ Config::CONFIG['DLEXT'] }}" )
11
+ rm Dir.glob( "java/**/*.{class,jar}" )
12
+ rm Dir.glob( "java/concurrent/FuturesService.java" )
13
+ end
14
+
15
+ def setup_extension( dir, lib_dir, extension )
16
+ ext = File.join( "ext", dir )
17
+ so_name = "#{ extension }.#{ Config::CONFIG['DLEXT'] }"
18
+ ext_so = File.join( ext, so_name )
19
+ lib_so = File.join( "lib", lib_dir, so_name )
20
+ ext_files = FileList[ File.join( ext, "*.{c,h}" ) ]
21
+ ext_makefile = File.join( ext, "Makefile" )
22
+ extconf_rb = File.join( ext, "extconf.rb" )
23
+
24
+ file ext_makefile => [ extconf_rb ] do
25
+ Dir.chdir ext do
26
+ ruby "extconf.rb"
27
+ end
28
+ end
29
+
30
+ file ext_so => ext_files + [ ext_makefile ] do
31
+ Dir.chdir ext do
32
+ case PLATFORM
33
+ when /win32/
34
+ sh 'nmake'
35
+ else
36
+ sh 'make'
37
+ end
38
+ end
39
+ end
40
+
41
+ file lib_so => [ ext_so ] do
42
+ cp ext_so, lib_so
43
+ end
44
+
45
+ task :compile => [ lib_so ]
46
+ end
47
+
48
+ case RUBY_PLATFORM
49
+ 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
+ file 'lib/concurrent/futures.jar' => [ 'java/concurrent/FuturesService.java' ] do
54
+ Dir.chdir( 'java' ) do
55
+ sh 'javac', '-classpath', File.join( ENV['JRUBY_HOME'], 'lib/jruby.jar' ), 'concurrent/FuturesService.java'
56
+ sh 'jar', 'cf', 'concurrent/futures.jar', *Dir.glob( 'concurrent/**/*.class' )
57
+ end
58
+ cp 'java/concurrent/futures.jar', 'lib/concurrent/futures.jar'
59
+ end
60
+ task :compile => [ "lib/concurrent/futures.jar" ]
61
+ else
62
+ setup_extension( 'concurrent/futures', 'concurrent', 'futures' )
63
+ end
64
+
65
+ desc "Compile extensions"
66
+ task :compile
67
+
68
+ task :test => [ :compile ]
69
+ Rake::TestTask.new do |task|
70
+ task.libs << 'lib'
71
+ task.libs << 'test'
72
+ task.test_files = [ "test/test_all.rb" ]
73
+ task.verbose = true
74
+ end
75
+
76
+ gemspec = Gem::Specification.new do |gemspec|
77
+ gemspec.name = "concurrent"
78
+ gemspec.version = "0.1"
79
+ gemspec.author = "MenTaLguY <mental@rydia.net>"
80
+ gemspec.summary = "Omnibus concurrency library for Ruby"
81
+ gemspec.test_file = 'test/test_all.rb'
82
+ gemspec.files = FileList[ 'Rakefile', 'test/*.rb', 'ext/**/*.{c,h,rb}',
83
+ 'java/**/*.{java,rb}',
84
+ "lib/**/*.{rb,jar,#{ Config::CONFIG['DLEXT'] }}" ]
85
+ gemspec.require_paths = [ 'lib' ]
86
+
87
+ case RUBY_PLATFORM
88
+ when /java/
89
+ gemspec.platform = 'jruby'
90
+ when /win32/
91
+ gemspec.platform = Gem::Platform::WIN32
92
+ else
93
+ gemspec.platform = Gem::Platform::RUBY
94
+ gemspec.extensions = FileList[ 'ext/**/extconf.rb' ]
95
+ end
96
+ end
97
+
98
+ task :package => [ :clean, :test ]
99
+ Rake::GemPackageTask.new( gemspec ) do |task|
100
+ task.gem_spec = gemspec
101
+ task.need_tar = true
102
+ end
103
+
104
+ task :default => [ :clean, :test ]
105
+
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('concurrent/futures')
@@ -0,0 +1,153 @@
1
+ /*
2
+ * concurrent/futures - futures and lazy evaluation for Ruby
3
+ *
4
+ * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
5
+ *
6
+ * This file is made available under the same terms as Ruby.
7
+ */
8
+
9
+ #include "ruby.h"
10
+ #include "rubysig.h"
11
+ #include "intern.h"
12
+
13
+ static VALUE mConcurrent;
14
+ static VALUE mFutures;
15
+ static VALUE eAsyncError;
16
+ static VALUE cThunk;
17
+
18
+ static ID value_id;
19
+ static ID inspect_id;
20
+ static ID respond_to_p_id;
21
+
22
+ typedef struct {
23
+ VALUE source;
24
+ VALUE value;
25
+ } Thunk;
26
+
27
+ static void thunk_copy_atomic(Thunk *to, Thunk const *from) {
28
+ int saved_critical;
29
+ saved_critical = rb_thread_critical;
30
+ rb_thread_critical = 1;
31
+ *to = *from;
32
+ rb_thread_critical = saved_critical;
33
+ }
34
+
35
+ static VALUE thunk_value(VALUE obj, int evaluate) {
36
+ VALUE original;
37
+
38
+ original = obj;
39
+
40
+ while ( CLASS_OF(obj) == cThunk ) {
41
+ Thunk *thunk;
42
+ Thunk copy;
43
+
44
+ Data_Get_Struct(obj, Thunk, thunk);
45
+ thunk_copy_atomic(&copy, thunk);
46
+
47
+ if (RTEST(copy.source)) {
48
+ if (evaluate) {
49
+ copy.value = rb_funcall(copy.source, value_id, 0);
50
+ copy.source = Qnil;
51
+ thunk_copy_atomic(thunk, &copy);
52
+ }
53
+
54
+ if ( obj != original ) {
55
+ Thunk *original_thunk;
56
+ Data_Get_Struct(original, Thunk, original_thunk);
57
+ thunk_copy_atomic(original_thunk, &copy);
58
+ }
59
+
60
+ if (!evaluate) {
61
+ break;
62
+ }
63
+ }
64
+
65
+ obj = copy.value;
66
+ }
67
+
68
+ return obj;
69
+ }
70
+
71
+ static VALUE thunk_eval(VALUE thunk) {
72
+ return thunk_value(thunk, 1);
73
+ }
74
+
75
+ static VALUE thunk_ground(VALUE thunk) {
76
+ return thunk_value(thunk, 0);
77
+ }
78
+
79
+ void thunk_mark(Thunk const *thunk) {
80
+ rb_gc_mark(thunk->source);
81
+ rb_gc_mark(thunk->value);
82
+ }
83
+
84
+ void thunk_free(Thunk *thunk) {
85
+ free(thunk);
86
+ }
87
+
88
+ static VALUE rb_thunk_new(VALUE klass, VALUE source) {
89
+ Thunk *thunk;
90
+
91
+ thunk = (Thunk *)malloc(sizeof(Thunk));
92
+
93
+ thunk->source = source;
94
+ thunk->value = Qnil;
95
+
96
+ return Data_Wrap_Struct(cThunk, thunk_mark, thunk_free, thunk);
97
+ }
98
+
99
+ static VALUE wrap_exception(VALUE unused, VALUE ex) {
100
+ rb_exc_raise(rb_funcall(eAsyncError, rb_intern("new"), 1, ex));
101
+ }
102
+
103
+ static VALUE rb_thunk_method_missing(int argc, VALUE *argv, VALUE self) {
104
+ ID name;
105
+
106
+ name = SYM2ID(argv[0]);
107
+ self = thunk_ground(self);
108
+
109
+ if ( CLASS_OF(self) == cThunk ) {
110
+ if ( name == inspect_id && argc == 1 ) {
111
+ Thunk *thunk;
112
+ 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(">"));
115
+ } else if ( name == respond_to_p_id && argc == 2 ) {
116
+ if ( ID2SYM(inspect_id) == argv[1] ||
117
+ ID2SYM(respond_to_p_id) == argv[1] )
118
+ {
119
+ return Qtrue;
120
+ }
121
+ }
122
+ }
123
+
124
+ self = rb_rescue2(thunk_eval, self, wrap_exception, Qnil, rb_cObject, 0);
125
+
126
+ return rb_funcall3(self, name, argc-1, argv+1);
127
+ }
128
+
129
+ static VALUE rb_thunk_value(VALUE self, VALUE thunk_v) {
130
+ return thunk_eval(thunk_v);
131
+ }
132
+
133
+ void Init_futures() {
134
+ value_id = rb_intern("value");
135
+ inspect_id = rb_intern("inspect");
136
+ respond_to_p_id = rb_intern("respond_to?");
137
+
138
+ mConcurrent = rb_define_module("Concurrent");
139
+ mFutures = rb_define_module_under(mConcurrent, "Futures");
140
+
141
+ eAsyncError = rb_define_class_under(mFutures, "AsyncError", rb_eRuntimeError);
142
+
143
+ cThunk = rb_class_boot(0); /* not Qnil */
144
+ rb_singleton_class(cThunk);
145
+ rb_undef_alloc_func(cThunk);
146
+ rb_const_set(mFutures, rb_intern("Thunk"), cThunk);
147
+
148
+ rb_define_singleton_method(cThunk, "new", rb_thunk_new, 1);
149
+ rb_define_singleton_method(cThunk, "value", rb_thunk_value, 1);
150
+ rb_define_private_method(cThunk, "method_missing",
151
+ rb_thunk_method_missing, -1);
152
+ }
153
+
@@ -0,0 +1,163 @@
1
+ require 'java'
2
+
3
+ begin
4
+ File.open(ARGV[0], "w") do |stream|
5
+ stream.print <<EOS
6
+ /***** BEGIN LICENSE BLOCK *****
7
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
8
+ *
9
+ * The contents of this file are subject to the Common Public
10
+ * License Version 1.0 (the "License"); you may not use this file
11
+ * except in compliance with the License. You may obtain a copy of
12
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
13
+ *
14
+ * Software distributed under the License is distributed on an "AS
15
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
16
+ * implied. See the License for the specific language governing
17
+ * rights and limitations under the License.
18
+ *
19
+ * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
20
+ *
21
+ * Alternatively, the contents of this file may be used under the terms of
22
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
23
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24
+ * in which case the provisions of the GPL or the LGPL are applicable instead
25
+ * of those above. If you wish to allow use of your version of this file only
26
+ * under the terms of either the GPL or the LGPL, and not to allow others to
27
+ * use your version of this file under the terms of the CPL, indicate your
28
+ * decision by deleting the provisions above and replace them with the notice
29
+ * and other provisions required by the GPL or the LGPL. If you do not delete
30
+ * the provisions above, a recipient may use your version of this file under
31
+ * the terms of any one of the CPL, the GPL or the LGPL.
32
+ ***** END LICENSE BLOCK *****/
33
+
34
+ package concurrent;
35
+
36
+ import java.io.IOException;
37
+
38
+ import org.jruby.Ruby;
39
+ import org.jruby.RubyClass;
40
+ import org.jruby.RubyException;
41
+ import org.jruby.exceptions.RaiseException;
42
+ import org.jruby.runtime.Block;
43
+ import org.jruby.runtime.CallbackFactory;
44
+ import org.jruby.runtime.callback.Callback;
45
+ import org.jruby.runtime.ObjectAllocator;
46
+ import org.jruby.runtime.load.BasicLibraryService;
47
+ import org.jruby.runtime.builtin.IRubyObject;
48
+
49
+ public class FuturesService implements BasicLibraryService {
50
+ public boolean basicLoad(final Ruby runtime) throws IOException {
51
+ Thunk.setup(runtime);
52
+ return true;
53
+ }
54
+
55
+ public static class Thunk implements IRubyObject {
56
+ private Ruby runtime;
57
+ private RubyClass klass;
58
+ private IRubyObject source;
59
+ private IRubyObject value;
60
+
61
+ Thunk(Ruby runtime, RubyClass klass, IRubyObject source) {
62
+ this.runtime = runtime;
63
+ this.klass = klass;
64
+ this.source = source;
65
+ this.value = null;
66
+ }
67
+
68
+ public static IRubyObject newInstance(IRubyObject recv, IRubyObject source, Block block) {
69
+ return new Thunk(recv.getRuntime(), (RubyClass)recv, source);
70
+ }
71
+
72
+ public static void setup(final Ruby runtime) throws IOException {
73
+ RubyClass cThunk = runtime.getOrCreateModule("Concurrent").defineModuleUnder("Futures").defineClassUnder("Thunk", null, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
74
+ 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));
77
+ }
78
+
79
+ public static IRubyObject thunkValue(IRubyObject obj, boolean evaluate) {
80
+ IRubyObject original=obj;
81
+
82
+ while (obj instanceof Thunk) {
83
+ Thunk thunk=(Thunk)obj;
84
+
85
+ synchronized (thunk) {
86
+ if ( thunk.source != null ) {
87
+ if (evaluate) {
88
+ thunk.value = thunk.source.callMethod(thunk.source.getRuntime().getCurrentContext(), "value");
89
+ thunk.source = null;
90
+ }
91
+
92
+ if ( obj != original ) {
93
+ Thunk original_thunk = (Thunk)original;
94
+ synchronized (original_thunk) {
95
+ original_thunk.value = thunk.value;
96
+ }
97
+ }
98
+
99
+ if (!evaluate) {
100
+ break;
101
+ }
102
+ }
103
+
104
+ obj = thunk.value;
105
+ }
106
+ }
107
+
108
+ return obj;
109
+ }
110
+
111
+ public static IRubyObject evalThunk(IRubyObject obj) {
112
+ try {
113
+ return thunkValue(obj, true);
114
+ } catch (RaiseException e) {
115
+ RubyClass cAsyncError = obj.getRuntime().getModule("Concurrent").defineModuleUnder("Futures").getClass("AsyncError");
116
+ RubyException e2 = (RubyException)cAsyncError.callMethod(obj.getRuntime().getCurrentContext(), "new", e.getException());
117
+ throw new RaiseException(e2);
118
+ }
119
+ }
120
+
121
+ public static IRubyObject value(IRubyObject recv, IRubyObject obj, Block block) {
122
+ return thunkValue(obj, true);
123
+ }
124
+
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(", ") });
148
+ }
149
+ EOS
150
+ end
151
+
152
+ stream.print <<EOS
153
+ }
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
@@ -0,0 +1,2 @@
1
+ require 'concurrent/actors/mailbox'
2
+ require 'concurrent/actors/actor'
@@ -0,0 +1,83 @@
1
+ #
2
+ # concurrent/actors/actor - class representing actors
3
+ #
4
+ # Copyright 2007 MenTaLguY <mental@rydia.net>
5
+ #
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # thi slist of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice
14
+ # this list of conditions and the following disclaimer in the documentatio
15
+ # and/or other materials provided with the distribution.
16
+ # * Neither the name of the Evan Phoenix nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ require 'thread'
33
+ begin
34
+ require 'fastthread'
35
+ rescue LoadError
36
+ end
37
+ require 'concurrent/core'
38
+ require 'concurrent/actors/mailbox.rb'
39
+
40
+ module Concurrent
41
+ module Actors
42
+
43
+ class Actor
44
+ class << self
45
+ alias :private_new :new
46
+ private :private_new
47
+
48
+ def spawn(&prc)
49
+ channel = Core::Channel.new
50
+ Thread.new do
51
+ channel << current
52
+ prc.call
53
+ end
54
+ channel.receive
55
+ end
56
+ alias :new :spawn
57
+
58
+ def current
59
+ Thread.current[:__current_actor__] ||= private_new(current_mailbox)
60
+ end
61
+
62
+ def current_mailbox
63
+ Thread.current[:__current_mailbox__] ||= Mailbox.new
64
+ end
65
+ private :current_mailbox
66
+
67
+ def receive(&prc)
68
+ current_mailbox.receive(&prc)
69
+ end
70
+ end
71
+
72
+ def initialize(mailbox)
73
+ @mailbox = mailbox
74
+ end
75
+
76
+ def <<(value)
77
+ @mailbox << value
78
+ self
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,109 @@
1
+ #
2
+ # concurrent/actors/mailbox - mailbox class supporting actor implementation
3
+ #
4
+ # Copyright 2007 MenTaLguY <mental@rydia.net>
5
+ #
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # thi slist of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice
14
+ # this list of conditions and the following disclaimer in the documentatio
15
+ # and/or other materials provided with the distribution.
16
+ # * Neither the name of the Evan Phoenix nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ require 'thread'
33
+ begin
34
+ require 'fastthread'
35
+ rescue LoadError
36
+ end
37
+
38
+ require 'concurrent/core'
39
+
40
+ module Concurrent
41
+ module Actors
42
+
43
+ class Mailbox
44
+ def initialize
45
+ @channel = Core::Channel.new
46
+ @skipped = []
47
+ end
48
+
49
+ # safe for multiple writers
50
+ def <<(value)
51
+ @channel << value
52
+ self
53
+ end
54
+
55
+ # safe only for a single reader
56
+ def receive
57
+ if block_given?
58
+ filter = Filter.new
59
+ yield filter
60
+
61
+ value = nil
62
+ action = nil
63
+
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
71
+ end
72
+ end
73
+ @skipped.delete_at found_at if found_at
74
+
75
+ until action
76
+ value = @channel.receive
77
+ action = filter.action_for value
78
+ @skipped.push value unless action
79
+ end
80
+
81
+ action.call value
82
+ else
83
+ unless @skipped.empty?
84
+ @skipped.shift
85
+ else
86
+ @channel.receive
87
+ end
88
+ end
89
+ end
90
+
91
+ class Filter
92
+ def initialize
93
+ @pairs = []
94
+ end
95
+
96
+ def when(pattern, &action)
97
+ raise ArgumentError, "no block given" unless action
98
+ @pairs.push [pattern, action]
99
+ end
100
+
101
+ def action_for(value)
102
+ pair = @pairs.find { |pattern, action| pattern === value }
103
+ pair ? pair[1] : nil
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,33 @@
1
+ # concurrent/core - pi calculus channels and other core bits
2
+ #
3
+ # Copyright 2007 MenTaLguY <mental@rydia.net>
4
+ #
5
+ # This file is made available under the same terms as Ruby.
6
+ #
7
+
8
+ require 'thread'
9
+ begin
10
+ require 'fastthread'
11
+ rescue LoadError
12
+ end
13
+
14
+ module Concurrent
15
+ module Core
16
+
17
+ # an asynchronous pi calculus channel
18
+ class Channel < Queue
19
+ alias :receive :shift
20
+ undef :clear
21
+ undef :deq
22
+ undef :empty?
23
+ undef :enq
24
+ undef :length
25
+ undef :num_waiting
26
+ undef :pop
27
+ undef :push
28
+ undef :shift
29
+ undef :size
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,151 @@
1
+ #
2
+ # concurrent/futures - futures and lazy evaluation for Ruby
3
+ #
4
+ # Copyright (C) 2007 MenTaLguY <mental@rydia.net>
5
+ #
6
+ # This file is made available under the same terms as Ruby.
7
+ #
8
+
9
+ require 'thread'
10
+ begin
11
+ require 'fastthread'
12
+ rescue LoadError
13
+ end
14
+ require 'thwait'
15
+
16
+ module Concurrent
17
+ module Futures
18
+ extend self
19
+
20
+ Future = self
21
+
22
+ class AsyncError < RuntimeError
23
+ attr_reader :reason
24
+
25
+ def initialize( reason, desc=nil )
26
+ super( desc )
27
+ @reason = reason
28
+ end
29
+ end
30
+
31
+ require 'concurrent/futures.so' # provides Thunk
32
+
33
+ def async( &block )
34
+ Thunk.new( Thread.new( &block ) )
35
+ end
36
+ alias new async
37
+ alias spawn async
38
+ alias future async
39
+
40
+ def await( future )
41
+ Thunk.value future
42
+ end
43
+
44
+ def await_any( *futures )
45
+ threads = futures.map { |future| Thread.new { Futures::await future } }
46
+ begin
47
+ threads.index ThreadsWait.new( *threads ).wait_next
48
+ ensure
49
+ threads.each { |thread| thread.raise RuntimeError }
50
+ end
51
+ end
52
+
53
+ AlreadyFulfilledError = Class.new StandardError
54
+
55
+ class Promise
56
+ def initialize
57
+ @lock = Mutex.new
58
+ @ready = ConditionVariable.new
59
+ end
60
+
61
+ def future
62
+ @future ||= Thunk.new self
63
+ end
64
+
65
+ def fulfill( value )
66
+ @lock.synchronize do
67
+ if defined? @value
68
+ raise AlreadyFulfilledError, "promise already fulfilled"
69
+ end
70
+ @value = value
71
+ @ready.broadcast
72
+ end
73
+ self
74
+ end
75
+
76
+ def fulfilled?
77
+ @lock.synchronize { defined? @value }
78
+ end
79
+
80
+ def value
81
+ @lock.synchronize do
82
+ @ready.wait @lock until defined? @value
83
+ @value
84
+ end
85
+ end
86
+
87
+ def fail( ex )
88
+ @lock.synchronize do
89
+ @value = Thunk.new( Thread.new { raise ex } )
90
+ end
91
+ self
92
+ end
93
+ end
94
+
95
+ class Lazy
96
+ def initialize( &block )
97
+ @lock = Mutex.new
98
+ end
99
+
100
+ def value
101
+ @lock.synchronize do
102
+ if @block
103
+ Support::TerminateWaitLock.synchronize do
104
+ @value = Thunk.new Thread.new( &block )
105
+ @block = nil
106
+ end
107
+ end
108
+ @value
109
+ end
110
+ end
111
+
112
+ def inspect
113
+ @lock.synchronize do
114
+ if @block
115
+ "#<Lazy pending #{ @block.inspect }>"
116
+ else
117
+ "#<Lazy requested #{ @value.inspect }>"
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def lazy( &block )
124
+ Thunk.new( Lazy.new( &block ) )
125
+ end
126
+
127
+ class Ref
128
+ def initialize( value=nil )
129
+ @lock = Mutex.new
130
+ @value = value
131
+ end
132
+
133
+ def exchange( value )
134
+ @lock.synchronize do
135
+ result = @value
136
+ @value = value
137
+ result
138
+ end
139
+ end
140
+
141
+ def modify( &block )
142
+ @lock.synchronize do
143
+ value = @value
144
+ @value = Future.async { block.call value }
145
+ end
146
+ end
147
+ end
148
+
149
+ end
150
+ end
151
+
@@ -0,0 +1,120 @@
1
+ #
2
+ # concurrent/parallel - data-parallel programming for Ruby
3
+ #
4
+ # Copyright (C) 2007 MenTaLguY <mental@rydia.net>
5
+ #
6
+ # This file is made available under the same terms as Ruby.
7
+ #
8
+
9
+ module Concurrent
10
+ module Parallel
11
+ end
12
+ end
13
+
14
+ module Enumerable
15
+ def parallel_each( n, &block )
16
+ parallel_subsets( n ).map do |slice|
17
+ Thread.new { slice.each &block }
18
+ end.each do |thread|
19
+ thread.join
20
+ end
21
+ self
22
+ end
23
+
24
+ def parallel_map( n, &block )
25
+ parallel_subsets( n ).map do |slice|
26
+ Thread.new { slice.map &block }
27
+ end.inject( [] ) do |a, thread|
28
+ a.push *thread.value
29
+ end
30
+ end
31
+
32
+ def parallel_select( n, &block )
33
+ parallel_subsets( n ).map do |slice|
34
+ Thread.new { slice.select &block }
35
+ end.inject( [] ) do |a, results|
36
+ a.push *thread.value
37
+ end
38
+ end
39
+
40
+ def parallel_reject( n, &block )
41
+ parallel_subsets( n ).map do |slice|
42
+ Thread.new { slice.reject &block }
43
+ end.inject( [] ) do |a, thread|
44
+ a.push *thread.value
45
+ end
46
+ end
47
+
48
+ def parallel_max( n )
49
+ parallel_subsets( n ).map do |slice|
50
+ Thread.new { slice.max }
51
+ end.map { |t| t.value }.max
52
+ end
53
+
54
+ def parallel_min( n )
55
+ parallel_subsets( n ).map do |slice|
56
+ Thread.new { slice.min }
57
+ end.map { |t| t.value }.min
58
+ end
59
+
60
+ def parallel_partition( n, &block )
61
+ parallel_subsets( n ).map do |slice|
62
+ Thread.new { slice.partition &block }
63
+ end.inject( [ [], [] ] ) do |acc, thread|
64
+ pair = thread.value
65
+ acc[0].push *pair[0]
66
+ acc[1].push *pair[1]
67
+ acc
68
+ end
69
+ end
70
+
71
+ def parallel_grep( re, n, &block )
72
+ parallel_subsets( n ).map do |slice|
73
+ Thread.new { slice.grep( re, &block ) }
74
+ end.inject( [] ) do |acc, thread|
75
+ acc.push *thread.value
76
+ end
77
+ end
78
+
79
+ def parallel_all?( n, &block )
80
+ parallel_subsets( n ).map do |slice|
81
+ Thread.new { slice.all? &block }
82
+ end.inject( true ) do |acc, thread|
83
+ acc && thread.value
84
+ end
85
+ end
86
+
87
+ def parallel_any?( n, &block )
88
+ parallel_subsets( n ).map do |slice|
89
+ Thread.new { slice.any? &block }
90
+ end.inject( false ) do |acc, thread|
91
+ acc || thread.value
92
+ end
93
+ end
94
+
95
+ def parallel_include?( n, obj )
96
+ parallel_subsets( n ).map do |slice|
97
+ Thread.new { slice.include? obj }
98
+ end.inject( false ) do |acc, thread|
99
+ acc || thread.value
100
+ end
101
+ end
102
+
103
+ def parallel_subsets( n )
104
+ to_a.parallel_subsets( n )
105
+ end
106
+ end
107
+
108
+ class Array
109
+ def parallel_subsets( n )
110
+ if n > 1
111
+ slice_size = size / n
112
+ (0...(( size.to_f / slice_size ).ceil)).map do |i|
113
+ self[i*slice_size, slice_size]
114
+ end
115
+ else
116
+ [ self ]
117
+ end
118
+ end
119
+ end
120
+
@@ -0,0 +1,11 @@
1
+ require 'test/unit'
2
+ require 'concurrent/actors'
3
+ require 'thread'
4
+
5
+ include Concurrent::Actors
6
+
7
+ class TestActors < Test::Unit::TestCase
8
+ def test_current
9
+ assert_instance_of Actor, Actor.current
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'test_actors.rb'
2
+ require 'test_futures.rb'
3
+ require 'test_parallel.rb'
@@ -0,0 +1,51 @@
1
+ require 'test/unit'
2
+ require 'concurrent/futures'
3
+ require 'thread'
4
+
5
+ include Concurrent::Futures
6
+
7
+ class FuturesTests < Test::Unit::TestCase
8
+ def test_promise_fulfill
9
+ promise = Promise.new
10
+ assert !promise.fulfilled?
11
+ promise.fulfill 10
12
+ assert promise.fulfilled?
13
+ assert_equal 10, promise.future
14
+ end
15
+
16
+ def test_promise_fulfill_twice
17
+ promise = Promise.new
18
+ promise.fulfill 10
19
+ assert_raise( AlreadyFulfilledError ) do
20
+ promise.fulfill 20
21
+ end
22
+ end
23
+
24
+ def test_promise_fail
25
+ promise = Promise.new
26
+ promise.fail( EOFError.new )
27
+ value = promise.future
28
+ assert_raise( EOFError ) do
29
+ Future.await promise.value
30
+ end
31
+ assert_raise( AsyncError ) do
32
+ value + 1
33
+ end
34
+ end
35
+
36
+ def test_future
37
+ f = Future.future { 3 }
38
+ assert_equal 3, f
39
+ end
40
+
41
+ def test_future_raise
42
+ f = Future.future { raise EOFError, "blah" }
43
+ assert_raise( EOFError ) do
44
+ Future.await f
45
+ end
46
+ assert_raise( AsyncError ) do
47
+ f + 1
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,34 @@
1
+ require 'test/unit'
2
+ require 'concurrent/parallel'
3
+ require 'thread'
4
+
5
+ class TestParallel < Test::Unit::TestCase
6
+ def check_parallel( enum, n, meth, *args, &block )
7
+ regular = enum.send( meth, *args, &block )
8
+ parallel = enum.send( "parallel_#{ meth }", n, *args, &block )
9
+ assert_equal regular, parallel
10
+ end
11
+
12
+ def test_map
13
+ check_parallel( 0..100, 2, "map" ) { |x| x * 2 }
14
+ end
15
+
16
+ def test_any?
17
+ check_parallel( 0..100, 2, "any?" ) { |x| ( ( x + 1 ) % 48 ).zero? }
18
+ check_parallel( 0..100, 2, "any?" )
19
+ check_parallel( [ false ] * 100, 2, "any?" )
20
+ check_parallel( [ true ] * 100, 2, "any?" )
21
+ end
22
+
23
+ def test_all?
24
+ check_parallel( 0..100, 2, "all?" ) { |x| ( ( x + 1 ) % 48 ).zero? }
25
+ check_parallel( 0..100, 2, "all?" )
26
+ check_parallel( [ false ] * 100, 2, "all?" )
27
+ check_parallel( [ true ] * 100, 2, "all?" )
28
+ end
29
+
30
+ def test_include?
31
+ check_parallel( 0..100, 2, "include?", 5 )
32
+ check_parallel( 0..100, 2, "include?", 1000 )
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: concurrent
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2007-05-24 00:00:00 -04:00
8
+ summary: Omnibus concurrency library for Ruby
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - MenTaLguY <mental@rydia.net>
30
+ files:
31
+ - Rakefile
32
+ - test/test_all.rb
33
+ - test/test_futures.rb
34
+ - test/test_actors.rb
35
+ - test/test_parallel.rb
36
+ - ext/concurrent/futures/futures.c
37
+ - ext/concurrent/futures/extconf.rb
38
+ - java/concurrent/FuturesService.java.rb
39
+ - lib/concurrent/actors.rb
40
+ - lib/concurrent/futures.rb
41
+ - lib/concurrent/parallel.rb
42
+ - lib/concurrent/core.rb
43
+ - lib/concurrent/actors/actor.rb
44
+ - lib/concurrent/actors/mailbox.rb
45
+ test_files:
46
+ - test/test_all.rb
47
+ rdoc_options: []
48
+
49
+ extra_rdoc_files: []
50
+
51
+ executables: []
52
+
53
+ extensions:
54
+ - ext/concurrent/futures/extconf.rb
55
+ requirements: []
56
+
57
+ dependencies: []
58
+