minx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .yardoc
23
+ doc
24
+ minx.gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Daniel Schierbeck
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,50 @@
1
+ Minx
2
+ ====
3
+
4
+ Massive and pervasive concurrency with Minx!
5
+
6
+ Minx uses the powerful concurrency primitives outlined by Tony Hoare in his
7
+ famous book "Communicating Sequential Processes".
8
+
9
+
10
+ Usage
11
+ -----
12
+
13
+ Minx lets you easily create concurrent programs using the notion of *processes*
14
+ and *channels*.
15
+
16
+ # Very contrived example...
17
+ chan = Minx.channel
18
+
19
+ Minx.spawn { chan.send("Hello, World!") }
20
+ Minx.spawn { puts chan.receive("Hello, World!") }
21
+
22
+ These primitives, although simple, are incredibly powerful. When reading from
23
+ or writing to a channel, a process yields execution -- and thus blocks until
24
+ another process also participates in the communication. An example of when
25
+ this would be useful is a simple network server:
26
+
27
+ # Create a channel for the incoming requests.
28
+ requests = Minx.channel
29
+
30
+ # Spawn 10 workers.
31
+ 10.times do
32
+ Minx.spawn do
33
+ requests.each {|request| handle_request(request) }
34
+ end
35
+ end
36
+
37
+ In the near future, evented IO will be implemented, allowing for highly
38
+ performant network and file applications.
39
+
40
+
41
+ Documentation
42
+ -------------
43
+
44
+ See [the full documentation](http://yardoc.org/docs/dasch-minx/file:README.md).
45
+
46
+
47
+ Copyright
48
+ ---------
49
+
50
+ Copyright (c) 2010 Daniel Schierbeck. See {file:LICENSE} for details.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "minx"
8
+ gem.summary = %Q{Massive and pervasive concurrency}
9
+ gem.description = %Q{An implementation of the CSP concurrency primitives}
10
+ gem.email = "daniel.schierbeck@gmail.com"
11
+ gem.homepage = "http://github.com/dasch/minx"
12
+ gem.authors = ["Daniel Schierbeck"]
13
+ gem.add_development_dependency "shoulda", ">= 0"
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ begin
45
+ require 'yard'
46
+ require 'yard/rake/yardoc_task'
47
+ YARD::Rake::YardocTask.new do |t|
48
+ extra_files = %w(LICENSE)
49
+ t.files = ['lib/**/*.rb']
50
+ t.options = ["--files=#{extra_files.join(',')}"]
51
+ end
52
+ rescue LoadError
53
+ task :yard do
54
+ abort "YARD is not available. In order to run yard, you must: sudo gem install yard"
55
+ end
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,31 @@
1
+
2
+ require 'benchmark'
3
+
4
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
5
+
6
+ require 'minx'
7
+
8
+
9
+ # Number of processes.
10
+ N = 10000
11
+
12
+ # Number of rounds.
13
+ M = 10
14
+
15
+ channels = (0...N).map { Minx::Channel.new }
16
+ processes = (0...N).each do |i|
17
+ Minx.spawn do
18
+ M.times do
19
+ value = channels[i].receive
20
+ channels[(i + 1) % N].send(value + 1)
21
+ end
22
+ end
23
+ end
24
+
25
+ Benchmark.bmbm do |bm|
26
+ bm.report("Sending a value #{M} times around a #{N}-length ring") do
27
+ Minx.spawn do
28
+ channels[0].send(0)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,73 @@
1
+
2
+ require 'fiber'
3
+
4
+ Minx = Module.new
5
+
6
+ require 'minx/channel'
7
+ require 'minx/process'
8
+
9
+ module Minx
10
+ # Spawn a new process.
11
+ #
12
+ # @return [Process] a new process
13
+ def self.spawn(&block)
14
+ Process.new(&block).spawn
15
+ end
16
+
17
+ # Create a new channel.
18
+ #
19
+ # @return [Channel] a new channel
20
+ def self.channel
21
+ Channel.new
22
+ end
23
+
24
+ # Yield control to another process.
25
+ #
26
+ # The calling process will be resumed at a later point.
27
+ def self.yield(*args)
28
+ Fiber.yield(*args)
29
+ end
30
+
31
+ # Wait for the processes to yield execution.
32
+ #
33
+ # @return [nil]
34
+ def self.join(*processes)
35
+ processes.each do |process|
36
+ process.resume
37
+ end
38
+
39
+ return nil
40
+ end
41
+
42
+ # Select from a list of channels.
43
+ #
44
+ # The channels will be enumerated in order; the first one carrying a message
45
+ # will be picked, and the message will be returned.
46
+ #
47
+ # If none of the channels are readable, the calling process will yield until
48
+ # a channel is written to, unless <code>:skip => true</code> is passed as
49
+ # an option, in which case the call will just return +nil+.
50
+ #
51
+ # @example Non-blocking select
52
+ # Minx.select(chan1, chan2, :skip => true)
53
+ #
54
+ # @param choices [Channel] the channels to be selected among
55
+ # @return the first message received from any of the channels
56
+ def self.select(*choices)
57
+ options = choices.last.is_a?(Hash) ? choices.pop : {}
58
+
59
+ # If a choice is readable, just receive from that one.
60
+ choices.each do |choice|
61
+ return choice.receive if choice.readable?
62
+ end
63
+
64
+ # Return immediately if :skip => true
65
+ return if options[:skip]
66
+
67
+ # ... otherwise, wait for a channel to become readable.
68
+ choices.each do |choice|
69
+ choice.receive(:async => true)
70
+ end
71
+ Minx.yield
72
+ end
73
+ end
@@ -0,0 +1,74 @@
1
+
2
+ module Minx
3
+ # A Channel is used to transmit messages between processes in a synchronized
4
+ # manner.
5
+ class Channel
6
+ def initialize
7
+ @readers = []
8
+ @writers = []
9
+ end
10
+
11
+ # Write a message to the channel.
12
+ #
13
+ # If no readers are waiting, the calling process will block until one comes
14
+ # along.
15
+ #
16
+ # @param message the message to be transmitted
17
+ # @return [nil]
18
+ def send(message)
19
+ if @readers.empty?
20
+ @writers << Fiber.current
21
+
22
+ # Yield control
23
+ Minx.yield
24
+
25
+ # Yield a message back to a reader.
26
+ Minx.yield(message)
27
+ else
28
+ @readers.shift.resume(message)
29
+ end
30
+
31
+ return nil
32
+ end
33
+
34
+ alias :<< :send
35
+
36
+ # Read a message off the channel.
37
+ #
38
+ # If no messages have been written to the channel, the calling process will
39
+ # block, only resuming when a write occurs. This behavior can be suppressed
40
+ # by calling +receive+ with <code>:async => true</code>, in which case the
41
+ # call will return immediately; the next time the calling process yields,
42
+ # it may be resumed with a message from the channel.
43
+ #
44
+ # @option options [Boolean] :async (false) whether or not to block
45
+ # @return a message
46
+ def receive(options = {})
47
+ if @writers.empty?
48
+ @readers << Fiber.current
49
+ Minx.yield unless options[:async]
50
+ else
51
+ @writers.shift.resume
52
+ end
53
+ end
54
+
55
+ # Enumerate over the messages sent to the channel.
56
+ #
57
+ # @example Iterating over channel messages
58
+ # chan.each do |message|
59
+ # puts "Got #{message}!"
60
+ # end
61
+ #
62
+ # @yield [message]
63
+ def each
64
+ yield receive while true
65
+ end
66
+
67
+ # Returns whether there are any processes waiting to write.
68
+ #
69
+ # @return +true+ if you can receive a message from the channel
70
+ def readable?
71
+ return !@writers.empty?
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Minx
3
+ class Process
4
+ def initialize(&block)
5
+ raise ArgumentError unless block_given?
6
+ @fiber = Fiber.new { block.call }
7
+ end
8
+
9
+ # Spawn the process.
10
+ #
11
+ # The process will immediately take over execution, and the current
12
+ # fiber will yield.
13
+ def spawn
14
+ @fiber.resume
15
+ end
16
+
17
+ # Resume the process.
18
+ #
19
+ # This yields execution to the process.
20
+ def resume
21
+ @fiber.resume
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ require 'helper'
2
+
3
+ class ChannelTest < Test::Unit::TestCase
4
+ context "A Channel" do
5
+ setup do
6
+ @channel = Minx.channel
7
+ @data = []
8
+ @p1 = Minx::Process.new { @channel.send(:foo) }
9
+ @p2 = Minx::Process.new { @data << @channel.receive }
10
+ end
11
+
12
+ context "with first a reader, then a writer" do
13
+ setup do
14
+ @p2.spawn
15
+ @p1.spawn
16
+ end
17
+
18
+ should "be able to transmit a message" do
19
+ assert_equal :foo, @data.first
20
+ end
21
+ end
22
+
23
+ context "with first a writer, then a reader" do
24
+ setup do
25
+ @p1.spawn
26
+ @p2.spawn
27
+ end
28
+
29
+ should "be able to transmit a message" do
30
+ assert_equal :foo, @data.first
31
+ end
32
+ end
33
+
34
+ should "send a message with #<<" do
35
+ Minx.spawn { @channel << :bar }
36
+ assert_equal :bar, @channel.receive
37
+ end
38
+
39
+ should "iterate over messages on #each" do
40
+ Minx.spawn { [:foo, :bar, :baz].each {|msg| @channel.send(msg) } }
41
+
42
+ values = [:foo, :bar, :baz]
43
+ Minx.spawn do
44
+ @channel.each do |message|
45
+ assert_equal values.shift, message
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,69 @@
1
+
2
+ require 'helper'
3
+
4
+ class ChoiceTest < Test::Unit::TestCase
5
+ context "A simple choice between two channels" do
6
+ setup do
7
+ @chan1 = Minx.channel
8
+ @chan2 = Minx.channel
9
+ end
10
+
11
+ context "with a single writer" do
12
+ setup do
13
+ Minx.spawn { @chan2.send(42) }
14
+ end
15
+
16
+ should "receive from the channel with a writer" do
17
+ Minx.spawn do
18
+ assert_equal 42, Minx.select(@chan1, @chan2)
19
+ end
20
+ end
21
+ end
22
+
23
+ context "with writers on both channels" do
24
+ setup do
25
+ Minx.spawn { @chan1.send(666) }
26
+ Minx.spawn { @chan2.send(42) }
27
+ end
28
+
29
+ should "receive from the first channel specified" do
30
+ Minx.spawn do
31
+ assert_equal 666, Minx.select(@chan1, @chan2)
32
+ end
33
+ end
34
+
35
+ should "not receive from the second channel specified" do
36
+ Minx.spawn do
37
+ Minx.select(@chan1, @chan2)
38
+ assert_equal 42, @chan2.receive
39
+ end
40
+ end
41
+ end
42
+
43
+ context "with no writers" do
44
+ setup do
45
+ Minx.spawn { @value = Minx.select(@chan1, @chan2) }
46
+ end
47
+
48
+ should "block until a writer comes along" do
49
+ Minx.spawn { @chan1.send(42) }
50
+ assert_equal 42, @value
51
+ end
52
+
53
+ should "also block until the second channel gets written to" do
54
+ Minx.spawn { @chan2.send(666) }
55
+ assert_equal 666, @value
56
+ end
57
+ end
58
+
59
+ context "with :skip => true" do
60
+ setup do
61
+ Minx.spawn { @value = Minx.select(@chan1, @chan2, :skip => true) }
62
+ end
63
+
64
+ should "not block" do
65
+ assert_nil @value
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'minx'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,41 @@
1
+ require 'helper'
2
+
3
+ class ProcessTest < Test::Unit::TestCase
4
+ should "Raise ArgumentError if no block is given" do
5
+ assert_raise(ArgumentError) { Minx::Process.new }
6
+ end
7
+
8
+ context "A Minx process" do
9
+ setup do
10
+ @data = ""
11
+ @process = Minx::Process.new { @data.replace("foo") }
12
+ end
13
+
14
+ should "not execute initially" do
15
+ assert_equal "", @data
16
+ end
17
+
18
+ should "execute on #spawn" do
19
+ @process.spawn
20
+
21
+ assert_equal "foo", @data
22
+ end
23
+ end
24
+
25
+ context "A Minx process that yields initially" do
26
+ setup do
27
+ @process = Minx::Process.new { Minx.yield; @value = 42 }
28
+ end
29
+
30
+ should "be rescheduled and resumed" do
31
+ Minx.spawn do
32
+ @process.spawn
33
+ Minx.yield
34
+ end
35
+
36
+ Minx.join(@process)
37
+
38
+ assert_equal 42, @value
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Schierbeck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-07 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: An implementation of the CSP concurrency primitives
26
+ email: daniel.schierbeck@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.md
34
+ files:
35
+ - .gitignore
36
+ - LICENSE
37
+ - README.md
38
+ - Rakefile
39
+ - VERSION
40
+ - benchmarks/ring.rb
41
+ - lib/minx.rb
42
+ - lib/minx/channel.rb
43
+ - lib/minx/process.rb
44
+ - test/channel_test.rb
45
+ - test/choice_test.rb
46
+ - test/helper.rb
47
+ - test/process_test.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/dasch/minx
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Massive and pervasive concurrency
76
+ test_files:
77
+ - test/choice_test.rb
78
+ - test/channel_test.rb
79
+ - test/helper.rb
80
+ - test/process_test.rb