concurrently 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +8 -3
  4. data/README.md +70 -60
  5. data/RELEASE_NOTES.md +16 -1
  6. data/Rakefile +98 -14
  7. data/concurrently.gemspec +16 -12
  8. data/ext/mruby/io.rb +1 -1
  9. data/guides/Overview.md +191 -66
  10. data/guides/Performance.md +300 -102
  11. data/guides/Troubleshooting.md +28 -28
  12. data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
  13. data/lib/all/concurrently/error.rb +0 -3
  14. data/lib/all/concurrently/evaluation.rb +8 -12
  15. data/lib/all/concurrently/event_loop.rb +1 -1
  16. data/lib/all/concurrently/event_loop/fiber.rb +3 -3
  17. data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
  18. data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
  19. data/lib/all/concurrently/proc.rb +13 -13
  20. data/lib/all/concurrently/proc/evaluation.rb +29 -29
  21. data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
  22. data/lib/all/concurrently/proc/fiber.rb +3 -6
  23. data/lib/all/concurrently/version.rb +1 -1
  24. data/lib/all/io.rb +118 -41
  25. data/lib/all/kernel.rb +82 -29
  26. data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
  27. data/lib/mruby/kernel.rb +1 -1
  28. data/mrbgem.rake +28 -17
  29. data/mruby_builds/build_config.rb +67 -0
  30. data/perf/Ruby/stage.rb +23 -0
  31. data/perf/benchmark_call_methods.rb +32 -0
  32. data/perf/benchmark_call_methods_waiting.rb +52 -0
  33. data/perf/benchmark_wait_methods.rb +38 -0
  34. data/perf/mruby/stage.rb +8 -0
  35. data/perf/profile_await_readable.rb +10 -0
  36. data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
  37. data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
  38. data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
  39. data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
  40. data/perf/profile_wait.rb +7 -0
  41. data/perf/stage.rb +47 -0
  42. data/perf/stage/benchmark.rb +47 -0
  43. data/perf/stage/benchmark/code_gen.rb +29 -0
  44. data/perf/stage/benchmark/code_gen/batch.rb +41 -0
  45. data/perf/stage/benchmark/code_gen/single.rb +38 -0
  46. metadata +27 -23
  47. data/ext/mruby/array.rb +0 -19
  48. data/lib/Ruby/concurrently/error.rb +0 -4
  49. data/perf/_shared/stage.rb +0 -33
  50. data/perf/concurrent_proc_calls.rb +0 -49
  51. data/perf/concurrent_proc_calls_awaiting.rb +0 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c58b0ef45ef3aa5d6e404f75b1592cbb415814e
4
- data.tar.gz: bfb763b405d52aa08b52c0a8d423dbf6af9b2186
3
+ metadata.gz: a2433cf85e8e22dae4f8753c3f0bf8da68d0a307
4
+ data.tar.gz: 5fc75f82d8f23b989b9f73171cb11aa8dd7dc6e7
5
5
  SHA512:
6
- metadata.gz: 8315e508fca33b19a367f37b38a29e39ace0eab55ad22eb4d0f3ce075b8878ad2066e8487f0cb0f0eec18ab9c93cfaf38dd2e61d9ab812127581535a6ca4ce0d
7
- data.tar.gz: 1e3812a1f85bfcedae574ca758fe724af1e67a4377edf685066d9a800b53b8b12225244d0abcb34f00c9b8b1adb2036561dca36567eddb94656274d198072c21
6
+ metadata.gz: 6a69bfe30abb26229e12195fc514c1a2766c0cf1531a5c72a4bf872ef358deaa5e28eae06eae537786f82a159a0071695913b93cc367989d30b90cc489b18879
7
+ data.tar.gz: 28990344bb12e54ca9318f7396753e2a929cdbf80a8942ce2c00a2742b4e82a003c4e7c3db5e1f0001658bcbb815fd821cd3b1fc0bdbd8b1df0fddd2c30155cc
data/.gitignore CHANGED
@@ -1,5 +1,5 @@
1
1
  /.bundle
2
2
  /.yardoc
3
3
  /doc
4
- /test/mruby/testenv/mruby
4
+ /mruby_builds/*/
5
5
  /Gemfile.lock
data/.travis.yml CHANGED
@@ -1,16 +1,21 @@
1
1
  language: ruby
2
2
 
3
- script: rake $TEST_RUBY:test
3
+ script: rake test:$RUBY
4
4
  sudo: false
5
5
 
6
6
  rvm:
7
7
  - 2.4.1
8
8
  - 2.3.4
9
9
  - 2.2.7
10
+ - ruby-head
10
11
 
11
12
  env:
12
- - TEST_RUBY=ruby
13
+ - RUBY=ruby
13
14
 
14
15
  matrix:
15
16
  include:
16
- - env: TEST_RUBY=mruby
17
+ - env: RUBY=mruby[1.3.0]
18
+ - env: RUBY=mruby[master]
19
+ allow_failures:
20
+ - rvm: ruby-head
21
+ - env: RUBY=mruby[master]
data/README.md CHANGED
@@ -2,84 +2,86 @@
2
2
 
3
3
  [![Build Status](https://secure.travis-ci.org/christopheraue/m-ruby-concurrently.svg?branch=master)](http://travis-ci.org/christopheraue/m-ruby-concurrently)
4
4
 
5
- Concurrently is a concurrency framework for Ruby and mruby. With it, concurrent
6
- code can be written sequentially similar to async/await.
7
-
8
- The concurrency primitive of Concurrently is the concurrent proc. It is very
9
- similar to a regular proc. Calling a concurrent proc creates a concurrent
10
- evaluation which is kind of a lightweight thread: It can wait for stuff without
11
- blocking other concurrent evaluations.
12
-
13
- Under the hood, concurrent procs are evaluated inside fibers. They can wait for
14
- readiness of I/O or a period of time (or the result of other concurrent
15
- evaluations). The interface is comparable to plain Ruby:
16
-
17
- <table>
18
- <tr>
19
- <th>Plain Ruby</th>
20
- <th>Concurrently</th>
21
- </tr>
22
- <tr>
23
- <td><code>Fiber.new(&block).resume</code></td>
24
- <td><code>concurrent_proc(&block).call</code></td>
25
- </tr>
26
- <tr>
27
- <td><code>IO.select([io])</code></td>
28
- <td><code>io.await_readable</code></td>
29
- </tr>
30
- <tr>
31
- <td><code>IO.select(nil, [io])</code></td>
32
- <td><code>io.await_writable</code></td>
33
- </tr>
34
- <tr>
35
- <td><code>IO.select(nil, nil, nil, seconds)</code></td>
36
- <td><code>wait(seconds)</code></td>
37
- </tr>
38
- </table>
39
-
40
- Beyond the mere beautification of the interface, Concurrently also takes care
41
- of the management of the event loop and the coordination between all concurrent
42
- evaluations.
43
-
44
-
45
- ## A Basic Example
5
+ Concurrently is a concurrency framework for Ruby and mruby built upon
6
+ fibers. With it code can be evaluated independently in its own execution
7
+ context similar to a thread. Execution contexts are called *evaluations* in
8
+ Concurrently and are created with [Kernel#concurrently][]:
9
+
10
+ ```ruby
11
+ hello = concurrently do
12
+ wait 0.2 # seconds
13
+ "hello"
14
+ end
15
+
16
+ world = concurrently do
17
+ wait 0.1 # seconds
18
+ "world"
19
+ end
20
+
21
+ puts "#{hello.await_result} #{world.await_result}"
22
+ ```
23
+
24
+ In this example we have three evaluations: The root evaluation and two more
25
+ concurrent evaluations started by said root evaluation. The root evaluation
26
+ waits until both concurrent evaluations were concluded and then prints "hello
27
+ world".
28
+
29
+
30
+ ## Synchronization with events
31
+
32
+ Evaluations can be synchronized with certain events by waiting for them. These
33
+ events are:
34
+
35
+ * an elapsed time period ([Kernel#wait][]),
36
+ * readability and writability of IO ([IO#await_readable][],
37
+ [IO#await_writable][]) and
38
+ * the result of another evaluation ([Concurrently::Proc::Evaluation#await_result][]).
39
+
40
+ Since evaluations run independently they are not blocking other evaluations
41
+ while waiting.
42
+
43
+
44
+ ## Concurrent I/O
45
+
46
+ When doing I/O it is important to do it **non-blocking**. If the IO object is
47
+ not ready use [IO#await_readable][] and [IO#await_writable][] to await
48
+ readiness.
49
+
50
+ For more about non-blocking I/O, see the core ruby docs for
51
+ [IO#read_nonblock][] and [IO#write_nonblock][].
46
52
 
47
53
  This is a little server reading from an IO and printing the received messages:
48
54
 
49
55
  ```ruby
50
- server = concurrent_proc do |io|
56
+ # Let's start with creating a pipe to connect client and server
57
+ r,w = IO.pipe
58
+
59
+ # Server:
60
+ # We let the server code run concurrently so it runs independently. It reads
61
+ # from the pipe non-blocking and awaits readability if the pipe is not readable.
62
+ concurrently do
51
63
  while true
52
64
  begin
53
- puts io.read_nonblock 32
65
+ puts r.read_nonblock 32
54
66
  rescue IO::WaitReadable
55
- io.await_readable
67
+ r.await_readable
56
68
  retry
57
69
  end
58
70
  end
59
71
  end
60
- ```
61
-
62
- Now, we create a pipe and start the server with the read end of it:
63
-
64
- ```ruby
65
- r,w = IO.pipe
66
- server.call_detached r
67
- ```
68
-
69
- Finally, we write messages to the write end of the pipe every 0.5 seconds:
70
72
 
71
- ```ruby
73
+ # Client:
74
+ # The client writes to the pipe every 0.5 seconds
72
75
  puts "#{Time.now.strftime('%H:%M:%S.%L')} (Start time)"
73
-
74
76
  while true
75
77
  wait 0.5
76
78
  w.write Time.now.strftime('%H:%M:%S.%L')
77
79
  end
78
80
  ```
79
81
 
80
- The evaluation of the root fiber is effectively blocked by waiting or writing
81
- messages. But since the server runs concurrently it is not affected by this and
82
- happily prints its received messages.
82
+ The root evaluation is effectively blocked by waiting or writing messages.
83
+ But since the server runs concurrently it is not affected by this and happily
84
+ prints its received messages.
83
85
 
84
86
  This is the output:
85
87
 
@@ -105,7 +107,7 @@ This is the output:
105
107
  ## Supported Ruby Versions
106
108
 
107
109
  * Ruby 2.2.7+
108
- * mruby 1.3+ (or mruby-head until mruby 1.3 is released)
110
+ * mruby 1.3+
109
111
 
110
112
 
111
113
  ## Development
@@ -121,6 +123,14 @@ Concurrently is licensed under the Apache License, Version 2.0. Please see the
121
123
  file called LICENSE.
122
124
 
123
125
 
126
+ [Kernel#concurrently]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#concurrently-instance_method
127
+ [Kernel#wait]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#wait-instance_method
128
+ [IO#await_readable]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_readable-instance_method
129
+ [IO#await_writable]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_writable-instance_method
130
+ [Concurrently::Proc::Evaluation#await_result]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc/Evaluation#await_result-instance_method
131
+ [IO#read_nonblock]: https://ruby-doc.org/core/IO.html#method-i-read_nonblock
132
+ [IO#write_nonblock]: https://ruby-doc.org/core/IO.html#method-i-write_nonblock
133
+
124
134
  [installation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/file/guides/Installation.md
125
135
  [overview]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/file/guides/Overview.md
126
136
  [documentation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/index
data/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Release Notes
2
-
2
+
3
+ ## 1.1.0 (2017-07-10)
4
+
5
+ ### Improvements
6
+ * Improved error reporting
7
+ * Improved benchmarks and profiling and made them work for mruby
8
+ * Improved documentation
9
+ * Improved overall performance
10
+
11
+ ### Extended [IO](http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO) interface
12
+ * [#await_read](http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_read-instance_method)
13
+ * [#await_written](http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_written-instance_method)
14
+
15
+ ### Extended [Kernel](http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel) interface
16
+ * [#await_fastest](http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#await_fastest-instance_method)
17
+
3
18
  ## 1.0.0 (2017-06-26)
4
19
 
5
20
  ### Added Support for
data/Rakefile CHANGED
@@ -1,28 +1,112 @@
1
1
  Dir.chdir File.dirname __FILE__
2
2
 
3
- namespace :ruby do
4
- task :test do
5
- sh "rspec"
3
+ perf_dir = File.expand_path "perf"
4
+
5
+ # Ruby
6
+ ruby = {
7
+ test: "rspec" ,
8
+ benchmark: "ruby -Iperf/Ruby -rstage",
9
+ profile: "ruby -Iperf/Ruby -rstage" }
10
+
11
+ mruby_dir = File.expand_path "mruby_builds"
12
+ mruby = {
13
+ src: "#{mruby_dir}/_source",
14
+ cfg: "#{mruby_dir}/build_config.rb",
15
+ test: "#{mruby_dir}/test/bin/mrbtest",
16
+ benchmark: "#{mruby_dir}/benchmark/bin/mruby",
17
+ profile: "#{mruby_dir}/profile/bin/mruby" }
18
+
19
+ namespace :test do
20
+ desc "Run the Ruby test suite"
21
+ task :ruby do
22
+ sh ruby[:test]
23
+ end
24
+
25
+ desc "Run the mruby test suite"
26
+ task :mruby, [:reference] => "mruby:build" do
27
+ sh mruby[:test]
6
28
  end
7
29
  end
8
30
 
9
- namespace :mruby do
10
- mruby_env = File.expand_path "test/mruby/testenv"
11
- mruby_dir = "#{mruby_env}/mruby"
12
-
13
- file mruby_dir do
14
- sh "git clone --depth=1 git://github.com/mruby/mruby.git #{mruby_dir}"
31
+ desc "Run the Ruby and mruby test suites"
32
+ task test: %w(test:ruby test:mruby)
33
+
34
+ namespace :benchmark do
35
+ desc "Run the benchmark #{perf_dir}/benchmark_[name].rb with Ruby"
36
+ task :ruby, [:name, :batch_size] do |t, args|
37
+ args.with_defaults name: "wait_methods", batch_size: 100
38
+ file = "#{perf_dir}/benchmark_#{args.name}.rb"
39
+ sh "#{ruby[:benchmark]} #{file} #{args.batch_size}"
15
40
  end
16
41
 
17
- task test: mruby_dir do
18
- sh "cd #{mruby_dir} && MRUBY_CONFIG=#{mruby_env}/build_config.rb rake test"
42
+ desc "Run the benchmark #{perf_dir}/benchmark_[name].rb with mruby"
43
+ task :mruby, [:name, :batch_size] => "mruby:build" do |t, args|
44
+ args.with_defaults name: "wait_methods", batch_size: 100
45
+ file = "#{perf_dir}/benchmark_#{args.name}.rb"
46
+ sh "#{mruby[:benchmark]} #{file} #{args.batch_size}"
19
47
  end
48
+ end
49
+
50
+ desc "Run the benchmark #{perf_dir}/benchmark_[name].rb for Ruby and mruby"
51
+ task :benchmark, [:name, :batch_size] => "mruby:build" do |t, args|
52
+ args.with_defaults name: "wait_methods", batch_size: 100
53
+ file = "#{perf_dir}/benchmark_#{args.name}.rb"
54
+ sh "#{ruby[:benchmark]} #{file} #{args.batch_size}", verbose: false
55
+ sh "#{mruby[:benchmark]} #{file} #{args.batch_size} skip_header", verbose: false
56
+ end
20
57
 
21
- task clean: mruby_dir do
22
- sh "cd #{mruby_dir} && rake deep_clean"
58
+ namespace :profile do
59
+ desc "Create a code profile by running #{perf_dir}/profile_[name].rb with Ruby"
60
+ task :ruby, [:name] do |t, args|
61
+ args.with_defaults name: "call"
62
+ file = "#{perf_dir}/profile_#{args.name}.rb"
63
+ sh "#{ruby[:profile]} #{file}"
64
+ end
65
+
66
+ desc "Create a code profile by running #{perf_dir}/profile_[name].rb with mruby"
67
+ task :mruby, [:name] => "mruby:build" do |t, args|
68
+ args.with_defaults name: "call"
69
+ file = "#{perf_dir}/profile_#{args.name}.rb"
70
+ sh "#{mruby[:profile]} #{file}"
23
71
  end
24
72
  end
25
73
 
26
- task test: %w(ruby:test mruby:test)
74
+ namespace :mruby do
75
+ file mruby[:src] do
76
+ sh "git clone --depth=1 git://github.com/mruby/mruby.git #{mruby[:src]}"
77
+ end
78
+
79
+ desc "Checkout a tag or commit of the mruby source. Executes: git checkout reference"
80
+ task :checkout, [:reference] => mruby[:src] do |t, args|
81
+ args.with_defaults reference: 'master'
82
+ `cd #{mruby[:src]} && git fetch --tags`
83
+ current_ref = `cd #{mruby[:src]} && git rev-parse HEAD`
84
+ checkout_ref = `cd #{mruby[:src]} && git rev-parse #{args.reference}`
85
+ if checkout_ref != current_ref
86
+ Rake::Task['mruby:clean'].invoke
87
+ sh "cd #{mruby[:src]} && git checkout #{args.reference}"
88
+ end
89
+ end
90
+
91
+ desc "Build mruby"
92
+ task :build, [:reference] => :checkout do
93
+ sh "cd #{mruby[:src]} && MRUBY_CONFIG=#{mruby[:cfg]} rake"
94
+ end
95
+
96
+ desc "Clean the mruby build"
97
+ task clean: mruby[:src] do
98
+ sh "cd #{mruby[:src]} && MRUBY_CONFIG=#{mruby[:cfg]} rake deep_clean"
99
+ end
100
+
101
+ desc "Update the source of mruby"
102
+ task pull: :clean do
103
+ sh "cd #{mruby[:src]} && git pull"
104
+ end
105
+
106
+ desc "Delete the mruby source"
107
+ task delete: mruby[:src] do
108
+ sh "rm -rf #{mruby[:src]}"
109
+ end
110
+ end
27
111
 
28
112
  task default: :test
data/concurrently.gemspec CHANGED
@@ -4,19 +4,23 @@ Gem::Specification.new do |spec|
4
4
  spec.name = "concurrently"
5
5
  spec.version = Concurrently::VERSION
6
6
  spec.summary = %q{A concurrency framework based on fibers}
7
- spec.description = <<-DESC
8
- Concurrently is a concurrency framework for Ruby and mruby. With it, concurrent
9
- code can be written sequentially similar to async/await.
7
+ spec.description = <<'DESC'
8
+ Concurrently is a concurrency framework for Ruby and mruby based on
9
+ fibers. With it code can be evaluated independently in its own execution
10
+ context similar to a thread:
10
11
 
11
- The concurrency primitive of Concurrently is the concurrent proc. It is very
12
- similar to a regular proc. Calling a concurrent proc creates a concurrent
13
- evaluation which is kind of a lightweight thread: It can wait for stuff without
14
- blocking other concurrent evaluations.
15
-
16
- Under the hood, concurrent procs are evaluated inside fibers. They can wait for
17
- readiness of I/O or a period of time (or the result of other concurrent
18
- evaluations).
19
- DESC
12
+ hello = concurrently do
13
+ wait 0.2 # seconds
14
+ "hello"
15
+ end
16
+
17
+ world = concurrently do
18
+ wait 0.1 # seconds
19
+ "world"
20
+ end
21
+
22
+ puts "#{hello.await_result} #{world.await_result}"
23
+ DESC
20
24
 
21
25
  spec.homepage = "https://github.com/christopheraue/m-ruby-concurrently"
22
26
  spec.license = "Apache-2.0"
data/ext/mruby/io.rb CHANGED
@@ -29,7 +29,7 @@ class IO
29
29
  #
30
30
  # @see https://ruby-doc.org/core-1.9.3/IO.html#method-i-read_nonblock
31
31
  # Ruby's documentation for IO#read_nonblock
32
- def read_nonblock(maxlen, outbuf = nil)
32
+ def read_nonblock(maxlen, outbuf = '')
33
33
  if IO.select [self], nil, nil, 0
34
34
  sysread(maxlen, outbuf)
35
35
  else
data/guides/Overview.md CHANGED
@@ -1,64 +1,123 @@
1
1
  # An Overview of Concurrently
2
2
 
3
- This document is meant as a general overview of what can be done with
4
- Concurrently and how all its parts work together. For more information and
5
- examples about a topic follow the interspersed links to the documentation.
3
+ The [README][] already introduced the basic interface of Concurrently.
4
+ This document explores the underlying concepts and explains how all parts work
5
+ together. For even more details and examples about a specific topic follow the
6
+ interspersed links to the [API documentation][].
7
+
8
+ Let's start with the concept of an *evaluation*.
9
+
6
10
 
7
11
  ## Evaluations
8
12
 
9
- An evaluation is an atomic thread of execution leading to a result. It is
10
- similar to a thread or a fiber. It can be suspended and resumed independently
11
- from other evaluations. It is also similar to a future or a promise by
12
- providing access to its future result or offering the ability to inject a
13
- result manually. Once the evaluation has a result it is *concluded*.
13
+ An evaluation is an independent execution context. It is similar to a thread or
14
+ a fiber since it can be suspended and resumed independently from other
15
+ evaluations.
14
16
 
15
17
  Every ruby program already has an implicit [root evaluation][Concurrently::Evaluation]
16
- running. Calling a concurrent proc creates a [proc evaluation][Concurrently::Proc::Evaluation].
18
+ running. Unless you explicitly tell your program to evaluate code concurrently
19
+ it is evaluated in the root evaluation. The root evaluation runs as long as
20
+ your program is running. Thus it is never concluded and its result cannot be
21
+ awaited.
22
+
23
+ Evaluating code with `concurrently(&block)` is done in its own type of
24
+ [evaluation][Concurrently::Proc::Evaluation]. Contrary to the root evaluation,
25
+ this evaluation has an end with a result. Next to its similarity to a thread
26
+ resp. fiber it is also similar to a future or a promise. It provides access
27
+ to its (future) result and offers the ability to shortcut its execution by
28
+ manually injecting a result. Once the evaluation has a result it is *concluded*.
29
+
30
+ ```ruby
31
+ # This is the root evaluation
32
+
33
+ concurrently do
34
+ # This is a concurrent evaluation
35
+ end
36
+
37
+ concurrently do
38
+ # This is another concurrent evaluation
39
+ end
40
+ ```
41
+
42
+
43
+ ## Concurrent Evaluation of Code
44
+
45
+ Evaluating a piece of code concurrently involves three distinct phases:
17
46
 
18
- ## Concurrent Procs
47
+ 1 2 3
48
+ evaluation0 ---+-------------+--->
49
+ | |
50
+ evaluation1 `--+-----+----´
51
+ | |
52
+ t io
19
53
 
20
- The [concurrent proc][Concurrently::Proc] is Concurrently's concurrency
21
- primitive. It looks and feels just like a regular proc. In fact,
22
- [Concurrently::Proc][] inherits from `Proc`.
54
+ 1. Invocation: evaluation0 kicks off evaluation1 to process the code in. It
55
+ does it asynchronously by not waiting for evaluation1 to finish.
56
+ 2. Computation: evaluation0 and evaluation1 run independently from each
57
+ other. evaluation1 synchronizes itself with other events (e.g. with time or
58
+ I/O).
59
+ 3. Synchronization: If evaluation0 is interested in the result of evaluation1
60
+ it has to wait for it. This synchronizes evaluation1 with evaluation0 again.
61
+ If evaluation1 has not finished yet evaluation0 blocks until it has.
23
62
 
24
- Concurrent procs are created with [Kernel#concurrent_proc][]:
63
+ Every tool Concurrently offers is linked to one of these phases.
64
+
65
+
66
+ ### Invocation
67
+
68
+ To start evaluating code concurrently use [Kernel#concurrently][]:
25
69
 
26
70
  ```ruby
27
- concurrent_proc do
71
+ evaluation = concurrently do
28
72
  # code to run concurrently
29
73
  end
30
74
  ```
31
75
 
32
- Concurrent procs can be used the same way regular procs are. For example, they
33
- can be passed around or called multiple times with different arguments.
34
-
35
- [Kernel#concurrently] is a shortcut for [Concurrently::Proc#call_and_forget][]:
36
-
76
+ It returns immediately with a handle to the started evaluation. The evaluation
77
+ will be processed in the background.
78
+
79
+ [Kernel#concurrently][] is actually a shortcut for
80
+
37
81
  ```ruby
38
- concurrently do
82
+ evaluation = concurrent_proc do
39
83
  # code to run concurrently
40
- end
84
+ end.call_detached
85
+ ```
86
+
87
+ In general, you do not need to work with concurrent procs directly. Just use
88
+ [Kernel#concurrently][]. But concurrent procs give you a finer control over
89
+ how the code is evaluated. This comes in handy for optimizing performance.
41
90
 
42
- # is equivalent to:
43
91
 
44
- concurrent_proc do
92
+ #### Concurrent Procs
93
+
94
+ The [concurrent proc][Concurrently::Proc] looks and feels just like a regular
95
+ proc. In fact, [Concurrently::Proc][] inherits from `Proc`. It is created with
96
+ [Kernel#concurrent_proc][]:
97
+
98
+ ```ruby
99
+ conproc = concurrent_proc do
45
100
  # code to run concurrently
46
- end.call_and_forget
101
+ end
47
102
  ```
48
103
 
49
- ### Calling Concurrent Procs
104
+ Concurrent procs can be used the same way regular procs are. For example, they
105
+ can be passed around or called multiple times with different arguments.
50
106
 
51
- A concurrent proc has four methods to call it.
107
+ When called a concurrent proc kicks of an evaluation of its code. A concurrent
108
+ proc has four methods to call it. Depending on which method is used the code
109
+ is evaluated slightly differently.
52
110
 
53
- The first two evaluate the concurrent proc immediately in the foreground:
111
+ The first two methods evaluate the concurrent proc immediately in the
112
+ foreground:
54
113
 
55
- * [Concurrently::Proc#call][] blocks the (root or proc) evaluation it has been
56
- called from until its own evaluation is concluded. Then it returns the
57
- result. This behaves just like `Proc#call`.
58
- * [Concurrently::Proc#call_nonblock][] will not block the (root or proc)
59
- evaluation it has been called from if it needs to wait. Instead, it
60
- immediately returns its [evaluation][Concurrently::Proc::Evaluation]. If it
61
- can be evaluated without waiting it returns the result.
114
+ * [Concurrently::Proc#call][] blocks the evaluation it has been called from
115
+ until its own evaluation is concluded. Then it returns the result. This
116
+ behaves just like `Proc#call`.
117
+ * [Concurrently::Proc#call_nonblock][] will not block the evaluation it has
118
+ been called from if it needs to wait. Instead, it immediately returns its
119
+ own [evaluation][Concurrently::Proc::Evaluation]. If it can be evaluated
120
+ without waiting it returns the result.
62
121
 
63
122
  The other two schedule the concurrent proc to be run in the background. The
64
123
  evaluation is not started right away but is deferred until the the next
@@ -68,15 +127,30 @@ iteration of the event loop:
68
127
  * [Concurrently::Proc#call_and_forget][] does not give access to the evaluation
69
128
  and returns `nil`.
70
129
 
130
+ The different methods to call a concurrent proc have an impact on the execution
131
+ speed. In general, [Concurrently::Proc#call_detached][] represents a good
132
+ middle ground between ease of use and performance. For an in-depth analysis of
133
+ the performance implications of each call method have a look at the
134
+ [performance documentation][performance]. It offers a guide what to use if
135
+ every cpu cycle counts.
136
+
137
+
138
+ ### Computation
139
+
140
+ In the computation phase the evaluation works through its code. While doing so
141
+ it can synchronize itself with different events.
71
142
 
72
- ## Timing Code
143
+ All synchronization methods are named `await_*`. As usual, there is an exception
144
+ to the rule: Waiting an amount of time is done with [Kernel#wait][].
73
145
 
74
- To defer the current evaluation for a fixed time use [Kernel#wait][].
146
+ #### Synchronization with Time
147
+
148
+ To defer the current evaluation for a fixed amount of time use [Kernel#wait][].
75
149
 
76
150
  * Doing something after X seconds:
77
151
 
78
152
  ```ruby
79
- concurrent_proc do
153
+ concurrently do
80
154
  wait X
81
155
  do_it!
82
156
  end
@@ -85,7 +159,7 @@ To defer the current evaluation for a fixed time use [Kernel#wait][].
85
159
  * Doing something every X seconds. This is a timer:
86
160
 
87
161
  ```ruby
88
- concurrent_proc do
162
+ concurrently do
89
163
  loop do
90
164
  wait X
91
165
  do_it!
@@ -96,7 +170,7 @@ To defer the current evaluation for a fixed time use [Kernel#wait][].
96
170
  * Doing something after X seconds, every Y seconds, Z times:
97
171
 
98
172
  ```ruby
99
- concurrent_proc do
173
+ concurrently do
100
174
  wait X
101
175
  Z.times do
102
176
  do_it!
@@ -105,33 +179,33 @@ To defer the current evaluation for a fixed time use [Kernel#wait][].
105
179
  end
106
180
  ```
107
181
 
182
+ * Doing something at a given point in time:
108
183
 
109
- ## Handling I/O
184
+ ```ruby
185
+ concurrently do
186
+ time = Time.new(2042,7,10, 16,13,26) # 10 July 2042, 16:13:26
187
+ wait (time-Time.now).to_f
188
+ do_it!
189
+ end
190
+ ```
110
191
 
111
- Readiness of I/O is awaited with [IO#await_readable][] and [IO#await_writable][].
112
- To read and write from an IO concurrently you can use [IO#concurrently_read][]
113
- and [IO#concurrently_write][].
192
+ #### Synchronization with I/O
193
+
194
+ To read and write from an IO and wait until the operation is complete without
195
+ blocking other evaluations use [IO#await_read][] and [IO#await_written][].
114
196
 
115
197
  ```ruby
116
198
  r,w = IO.pipe
117
199
 
118
200
  concurrently do
119
201
  wait 1
120
- w.concurrently_write "Continue!"
121
- end
122
-
123
- concurrently do
124
- # This runs while r awaits readability.
202
+ w.await_written "Continue!"
125
203
  end
126
204
 
127
- concurrently do
128
- # This runs while r awaits readability.
129
- end
130
-
131
- # Read from r. It will take one second until there is input.
132
- message = r.concurrently_read 1024
133
205
 
134
- puts message # prints "Continue!"
206
+ # Read from r. It will take one second until there is input because r must
207
+ # wait until the string has been written to w.
208
+ r.await_read 1024 # prints "Continue!"
135
209
 
136
210
  r.close
137
211
  w.close
@@ -157,23 +231,69 @@ end
157
231
  ```
158
232
 
159
233
 
160
- ## Flow of Control
234
+ #### Synchronization with Results of Evaluations
235
+
236
+ Results of other evaluations can be waited for with
237
+ [Concurrently::Proc::Evaluation#await_result][]:
238
+
239
+ ```ruby
240
+ mailbox = concurrently do
241
+ wait 1
242
+ 'message'
243
+ end
244
+
245
+ forwarder = concurrently do
246
+ "FW: #{mailbox.await_result}"
247
+ end
248
+
249
+ # It will take one second until there is a message in the mailbox
250
+ puts forwarder.await_result # prints "FW: message"
251
+ ```
252
+
253
+ To wait for the fastest in a list of evaluations use
254
+ [Kernel#await_fastest][]:
255
+
256
+ ```ruby
257
+ mailbox1 = concurrently do
258
+ wait 1
259
+ 'slow message'
260
+ end
261
+
262
+ mailbox2 = concurrently do
263
+ wait 0.5
264
+ 'fast message'
265
+ end
266
+
267
+ mailbox = await_fastest(mailbox1, mailbox2)
268
+ mailbox.await_result # => "fast message"
269
+ ```
270
+
271
+
272
+ ### Synchronization
273
+
274
+ Synchronizing the invoking evaluation with the result of the invoked one is
275
+ done as described in the section about [synchronizing results of evaluations]
276
+ (#Synchronization_with_Results_of_Evaluations).
277
+
278
+
279
+ ## About the Event Loop
161
280
 
162
281
  To understand when code is run (and when it is not) it is necessary to know
163
282
  a little bit more about the way Concurrently works.
164
283
 
165
- Concurrently lets every (real) thread run an [event loop][Concurrently::EventLoop].
166
- These event loops are responsible for watching IOs and scheduling evaluations
167
- of concurrent procs. Evaluations are scheduled by putting them into a run queue
168
- ordered by the time they are supposed to run. The run queue is then worked off
169
- sequentially. If two evaluations are scheduled to run at the same time the
284
+ Concurrently lets every thread run an [event loop][Concurrently::EventLoop].
285
+ These event loops work silently in the background and are responsible for
286
+ watching IOs and scheduling evaluations. Evaluations are scheduled by putting
287
+ them into a run queue ordered by the time they are supposed to run. The run
288
+ queue is then worked off sequentially up to the point corresponding to the
289
+ current time. If two evaluations are scheduled to run at the same time the
170
290
  evaluation scheduled first is run first.
171
291
 
172
292
  Event loops *do not* run parallel to your application's code at the exact same
173
293
  time (e.g. on another cpu core). Instead, your code yields to them if it
174
294
  waits for something: **The event loop is (and only is) entered if your code
175
- calls `#wait` or one of the `#await_*` methods.** Later, when your code can
176
- be resumed the event loop schedules the corresponding evaluation to run again.
295
+ calls one of the synchronization methods.** Later, when your code can be
296
+ resumed the event loop schedules the corresponding evaluation to run again.
177
297
 
178
298
  Keep in mind, that an event loop **must never be interrupted, blocked or
179
299
  overloaded.** A healthy event loop is one that can respond to new events
@@ -317,6 +437,9 @@ Keep in mind, that to focus on the use of Concurrently the example does not
317
437
  take error handling for I/O, properly closing all connections and other details
318
438
  into account.
319
439
 
440
+ [README]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/file/README.md
441
+ [API documentation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/index
442
+ [performance]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/file/guides/Performance.md
320
443
  [Concurrently::Evaluation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Evaluation
321
444
  [Concurrently::Proc]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc
322
445
  [Concurrently::Proc#call]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call-instance_method
@@ -324,12 +447,14 @@ into account.
324
447
  [Concurrently::Proc#call_detached]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call_detached-instance_method
325
448
  [Concurrently::Proc#call_and_forget]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call_and_forget-instance_method
326
449
  [Concurrently::Proc::Evaluation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc/Evaluation
450
+ [Concurrently::Proc::Evaluation#await_result]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc/Evaluation#await_result-instance_method
327
451
  [Concurrently::EventLoop]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/EventLoop
328
452
  [Kernel#concurrent_proc]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#concurrent_proc-instance_method
329
453
  [Kernel#concurrently]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#concurrently-instance_method
330
454
  [Kernel#wait]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#wait-instance_method
455
+ [Kernel#await_fastest]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#await_fastest-instance_method
331
456
  [IO#await_readable]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_readable-instance_method
332
457
  [IO#await_writable]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_writable-instance_method
333
- [IO#concurrently_read]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#concurrently_read-instance_method
334
- [IO#concurrently_write]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#concurrently_write-instance_method
458
+ [IO#await_read]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_read-instance_method
459
+ [IO#await_written]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_written-instance_method
335
460
  [Troubleshooting]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/file/guides/Troubleshooting.md