cfiber 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ ## 0.0.1
2
+
3
+ * Initial setup
@@ -0,0 +1,20 @@
1
+ Copyright 2010 Jean-Denis Vauguet
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,54 @@
1
+ CFiber is [Fibers](http://rubydoc.info/stdlib/core/1.9.2/Fiber) implemented using continuations, using the native Ruby [Continuation](http://rubydoc.info/stdlib/core/1.9.2/Continuation) class.
2
+
3
+ Experimental! It is based on [tmm1's work](https://gist.github.com/48802).
4
+
5
+ # Synopsis
6
+
7
+ Continuations in Ruby are like time-machine engineers. They let you take snapshots of your runtime environment, and jump (back) to a particular "save point" at any time. Continuation objects, which you may store, are like an object representing a frozen point in time where one may jump back and start a new life. Fibers are a way to pause and resume an execution frame by hand, scheduling its execution within the wrapping thread (which may be another Fiber!).
8
+
9
+ All in all, it is all about jumping back and forth between several states, so with some smart design, continuations should let us achieve the Fiber pattern! This project is an attempt at implementing the Ruby 1.9 Fiber API using only continuations. The Continuation API is made of only two methods:
10
+
11
+ * `#callcc`, which stands for *Call With Current Continuation*; it allows for capturing the runtime state and associate a pending block to it. The "current continuation" is a way to talk about the "remaining/pending code left to run" at the time the continuation is captured. In a way, capturing a new continuation sets a checkpoint which allows you to jump back to a particular state, so you effectively gain access to "the remaining code at this time" for you now have a way to jump back in the past to reach what was the current state (that's for the "past/current" confusion);
12
+ * `#call`, which have us jump back in time and trigger the block in the context of the current runtime, where "current" really means "the captured state" (again).
13
+
14
+ Those are the basis for CFiber. See the *Interesting readings* section for more information.
15
+
16
+ ## Installation
17
+
18
+ Not available as a gem yet. You should clone this repository.
19
+
20
+ You may build and install a local gem running `gem build cfiber.gemspec && gem install cfiber*.gem` though.
21
+
22
+ ## Usage
23
+
24
+ Run `ruby spec/cfiber_spec.rb`. Run and **read** `ruby examples/use_cases.rb`. Then play with `Fiber`.
25
+
26
+ In a Ruby console, `require ./lib/cfiber.rb` (when inside cfiber's root directory) or `require 'cfiber'` (if you built and installed a local gem).
27
+
28
+ By installing the [Logg](http://github.com/chikamichi/logg) gem, you'll gain debugging output. Pass the `-d` flag: `ruby examples/use_cases.rb`.
29
+
30
+ ## TODO
31
+
32
+ * coroutines using `#transfer` do not work atm.
33
+ * `#alive?` implementation is challenging
34
+
35
+ ## Interesting readings
36
+
37
+ ### Doc
38
+
39
+ * http://rubydoc.info/stdlib/core/1.9.2/Fiber
40
+ * http://rubydoc.info/stdlib/core/1.9.2/Continuation
41
+
42
+ ### Continuations
43
+
44
+ * http://www.intertwingly.net/blog/2005/04/13/Continuations-for-Curmudgeons
45
+ * http://gnuu.org/2009/03/21/demystifying-continuations-in-ruby/
46
+ * http://onestepback.org/articles/callcc/
47
+ * http://www.ibm.com/developerworks/java/library/os-lightweight9/
48
+
49
+ ### Fibers
50
+
51
+ * http://masanjin.net/blog/fibers
52
+ * http://www.davidflanagan.com/2007/08/coroutines-via-fibers-in-ruby-19.html
53
+ * http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html
54
+ * http://blog.zacharyvoase.com/2010/01/02/nice-redis-em/
@@ -0,0 +1,172 @@
1
+ # Based on this 1.8 try: https://gist.github.com/48802
2
+
3
+ class FiberError < StandardError; end
4
+ class Fiber
5
+ require 'continuation'
6
+ require './lib/cfiber/debug'
7
+
8
+ attr_reader :alive
9
+ attr_accessor :state
10
+
11
+ # Like an implicit Fiber.yield, the first one: it acts the same as
12
+ # Fiber.yield but for the &block call!
13
+ #
14
+ def initialize(&block)
15
+ @alive = true
16
+ unless @yield = callcc { |cc| cc }
17
+ @args = Fiber.current.instance_exec(@args, &block)
18
+ @resume.call
19
+ end
20
+ self.class.log.debug "initial state for #{self}: #{@yield}"
21
+ end
22
+
23
+ def self.current
24
+ Thread.current[:__cfiber]
25
+ end
26
+
27
+ # Fiber.yield delegates to the current fiber instance #yield method.
28
+ #
29
+ # In order to retrieve the current fiber, a reference should have been
30
+ # stored. It's thread-safe for it uses a Thread.current-local variable to
31
+ # store the ref.
32
+ #
33
+ def self.yield(*args)
34
+ Fiber.current.yield(*args)
35
+ end
36
+
37
+ # Wake a fiber up, passing its args to the resumed block.
38
+ #
39
+ # First, it captures a new continuation to save a breakpoint, then it yields
40
+ # its arguments. Along the way, a reference for the current fiber object is
41
+ # stored within the current thread.
42
+ #
43
+ def resume(*args)
44
+ # Using callcc as a breakpoint, storing the continuation outside the block
45
+ # scope. This allows for the lazy-evaluation of an else clause.
46
+ if @resume = callcc { |cc| cc }
47
+ Fiber.current = self
48
+ Fiber.current.state = :awake
49
+ @args = args.size > 1 ? args : args.first
50
+ @yield.call # the first #resume call will trigger the #initialize block
51
+ # execution, until a call to Fiber.yield is made inside the
52
+ # very block; whereas subsequent #resume calls will resume
53
+ # the block's execution from the last Fiber.yield point.
54
+ else
55
+ log.debug "outputing inside #resume for #{self}"
56
+ @args
57
+ end
58
+ rescue NoMethodError => e # undefined method call for nil:NilClass, that is
59
+ @alive = false
60
+ raise FiberError, "dead fiber #{Fiber.current} called"
61
+ end
62
+
63
+ # Pause the current fiber, while yielding its args in the fiber's context.
64
+ #
65
+ # First, it captures a new continuation to save a breakpoint, then it yields
66
+ # its arguments ("passing along any arguments that were passed to it").
67
+ #
68
+ def yield(*args)
69
+ if @yield = callcc { |cc| cc }
70
+ @args = args.size > 1 ? args : args.first
71
+ @resume.call
72
+ else
73
+ log.debug "outputing inside #yield for #{self}"
74
+ @args
75
+ end
76
+ rescue NoMethodError => e # undefined method call for nil:NilClass, that is
77
+ @alive = false
78
+ raise FiberError, "dead fiber #{Fiber.current} called"
79
+ end
80
+
81
+ def transfer_yield(*args)
82
+ self.state = :paused
83
+ unless @yield = callcc { |cc| cc }
84
+ puts "yielding #{self}"
85
+ @resume.call
86
+ end
87
+ end
88
+
89
+ # Useful to implement coroutines.
90
+ #
91
+ # @example
92
+ # f = g = nil
93
+ #
94
+ # f = Fiber.new do |x|
95
+ # puts "F1: #{x}"
96
+ # x = g.transfer(x+1)
97
+ # puts "F2: #{x}"
98
+ # x = g.transfer(x+1)
99
+ # puts "F3: #{x}"
100
+ # end
101
+ #
102
+ # g = Fiber.new do |x|
103
+ # puts "G1: #{x}"
104
+ # x = f.transfer(x+1)
105
+ # puts "G2: #{x}"
106
+ # x = f.transfer(x+1)
107
+ # end
108
+ #
109
+ # f.transfer(100)
110
+ #
111
+ # -----------------
112
+ #
113
+ # g = Fiber.new { |x|
114
+ # puts "G1: #{x}"
115
+ # x = Fiber.yield(x+1) # f.transfer => Thread.current[:__cfiber_transfer] != nil => Thread.current[:__cfiber_transfer] = nil && Fiber.yield
116
+ # puts "G2: #{x}"
117
+ # x = Fiber.yield(x+1)
118
+ # }
119
+ #
120
+ # f = Fiber.new { |x|
121
+ # puts "F1: #{x}"
122
+ # x = g.resume(x+1) # g.transfer => Thread.current[:__cfiber_transfer] = Fiber.current && g.resume
123
+ # puts "F2: #{x}"
124
+ # x = g.resume(x+1) # pareil
125
+ # puts "F3: #{x}"
126
+ # }
127
+ #
128
+ # f.resume(100) # f.transfer => Fiber.current == nil => f.resume
129
+ #
130
+ # FIXME: the tricky part is init. In the example above, f.transfer(100)
131
+ # triggers f.resume(100), which in turns reach g.transfer(101). This
132
+ # triggers g.yield(f.resume(101)) but this is wrong. One must atomically
133
+ # pause (yield) g and resume f.
134
+ #
135
+ def transfer(*args)
136
+ if Fiber.current.nil?
137
+ log.debug ""
138
+ log.debug "cas 1"
139
+ Thread.current[:__cfiber_transfer] = nil
140
+ self.resume(*args)
141
+
142
+ elsif Thread.current[:__cfiber_transfer].nil?
143
+ log.debug ""
144
+ log.debug "cas 2"
145
+ log.debug "resuming #{self}"
146
+ Thread.current[:__cfiber_transfer] = Fiber.current
147
+ self.resume(*args)
148
+
149
+ elsif Thread.current[:__cfiber_transfer].is_a?(Fiber)
150
+ log.debug ""
151
+ log.debug "cas 3"
152
+ log.debug "pausing #{Fiber.current}"
153
+ Thread.current[:__cfiber_transfer] = nil
154
+ Fiber.yield(*args)
155
+ #Fiber.yield(self.resume(*args)) # TODO: handle multiple coroutines
156
+ else
157
+ log.debug ""
158
+ log.debug "cas 4"
159
+ raise FiberError, "transfer mismatch"
160
+ end
161
+ end
162
+
163
+ def alive?
164
+ Fiber.current.instance_variable_get(:@alive)
165
+ end
166
+
167
+ private
168
+
169
+ def self.current=(fiber)
170
+ Thread.current[:__cfiber] = fiber
171
+ end
172
+ end
@@ -0,0 +1,25 @@
1
+ class Fiber
2
+ begin
3
+ require 'logg'
4
+ include Logg::Machine
5
+
6
+ log.as(:debug) do |msg|
7
+ if $DEBUG
8
+ puts "[DEBUG] #{msg}"
9
+ end
10
+ end
11
+
12
+ log.debug 'output ready'
13
+ rescue LoadError
14
+ def self.log
15
+ Object.new.extend( Module.new do
16
+ def debug(*args, &block)
17
+ # do nothing
18
+ end
19
+ end)
20
+ end
21
+ def log
22
+ self.class.log
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ module CFiber
2
+ MAJOR = 0
3
+ MINOR = 0
4
+ PATCH = 2
5
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
6
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cfiber
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jean-Denis Vauguet <jd@vauguet.fr>
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-28 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: logg
16
+ requirement: &75456510 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *75456510
25
+ description: Continuations used to implement Fibers as provided by Ruby 1.9. Works
26
+ in 1.8 as well.
27
+ email: jd@vauguet.fr
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/cfiber.rb
33
+ - lib/cfiber/debug.rb
34
+ - lib/cfiber/version.rb
35
+ - MIT-LICENSE
36
+ - README.md
37
+ - CHANGELOG.md
38
+ homepage: http://www.github.com/chikamichi/cfiber
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.6
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Ruby Fibers using Ruby Continuations
62
+ test_files: []