minx 0.1.0

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,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