concurrent 0.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.
@@ -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
+