cfiber 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +54 -0
- data/lib/cfiber.rb +172 -0
- data/lib/cfiber/debug.rb +25 -0
- data/lib/cfiber/version.rb +6 -0
- metadata +62 -0
data/CHANGELOG.md
ADDED
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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/
|
data/lib/cfiber.rb
ADDED
@@ -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
|
data/lib/cfiber/debug.rb
ADDED
@@ -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
|
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: []
|