green 0.0.1
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.
- data/Gemfile +7 -0
- data/Gemfile.lock +34 -0
- data/README.md +55 -0
- data/Rakefile +131 -0
- data/Readme.md +55 -0
- data/app.ru +24 -0
- data/green.gemspec +48 -0
- data/lib/green/event.rb +29 -0
- data/lib/green/ext.rb +25 -0
- data/lib/green/group.rb +78 -0
- data/lib/green/hub/em.rb +44 -0
- data/lib/green/hub.rb +42 -0
- data/lib/green/monkey.rb +13 -0
- data/lib/green/semaphore.rb +44 -0
- data/lib/green/tcp_socket.rb +25 -0
- data/lib/green-em/em-http.rb +29 -0
- data/lib/green.rb +117 -0
- metadata +64 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
green (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
addressable (2.2.7)
|
10
|
+
em-http-request (1.0.0)
|
11
|
+
addressable (>= 2.2.3)
|
12
|
+
em-socksify
|
13
|
+
eventmachine (>= 1.0.0.beta.3)
|
14
|
+
http_parser.rb (>= 0.5.2)
|
15
|
+
em-socksify (0.2.0)
|
16
|
+
eventmachine (>= 1.0.0.beta.4)
|
17
|
+
eventmachine (1.0.0.mail.7)
|
18
|
+
http_parser.rb (0.5.3)
|
19
|
+
kgio (2.7.4)
|
20
|
+
rack (1.4.1)
|
21
|
+
raindrops (0.8.0)
|
22
|
+
unicorn (4.2.1)
|
23
|
+
kgio (~> 2.6)
|
24
|
+
rack
|
25
|
+
raindrops (~> 0.7)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
em-http-request
|
32
|
+
eventmachine (~> 1.0.0.beta.4)
|
33
|
+
green!
|
34
|
+
unicorn
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
Cooperative multitasking for Ruby. Proof of concept.
|
2
|
+
|
3
|
+
Based on Ruby 1.9 Fibers, but unlike EM::Synchrony it uses symmetric coroutines (only #current and #transfer used) and HUB-orientend architecture. So coroutines transfer control to HUB and HUB transfer control to coroutines. Coroutines never tranfer control to each other.
|
4
|
+
|
5
|
+
In comparison with EM-Synchrony it allows:
|
6
|
+
- develop real complex cooperative multitasking apps;
|
7
|
+
- timeouts. Yes, in common case you cannot add timeout with EM-Synchrony;
|
8
|
+
- kill greens. And it safe unlike kill Threads;
|
9
|
+
- works with REPL and debugger. EM-Synchrony uses Fiber.yield, so you cannot run nothing in REPL;
|
10
|
+
- works with every environment. You can run nonblock web-applications with Unicorn;
|
11
|
+
- compatible with Ruby's Enumerator and with any other uses of Fibers themself (see https://github.com/igrigorik/em-synchrony/issues/114)
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'green'
|
15
|
+
require 'green/group'
|
16
|
+
require 'green-em/em-http'
|
17
|
+
|
18
|
+
g = Green::Pool.new(size: 2)
|
19
|
+
|
20
|
+
urls = ['http://google.com', 'http://yandex.ru']
|
21
|
+
|
22
|
+
results = g.enumerator(urls) do |url|
|
23
|
+
EventMachine::HttpRequest.new(url).get
|
24
|
+
end.map { |i| i.response }
|
25
|
+
|
26
|
+
p results
|
27
|
+
```
|
28
|
+
|
29
|
+
You can run it from Irb! ;)
|
30
|
+
|
31
|
+
You can add timeout:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'green'
|
35
|
+
require 'green/group'
|
36
|
+
require 'green-em/em-http'
|
37
|
+
|
38
|
+
g = Green::Pool.new(size: 2)
|
39
|
+
|
40
|
+
urls = ['http://google.com', 'http://yandex.ru']
|
41
|
+
|
42
|
+
begin
|
43
|
+
Green.timeout(1) do
|
44
|
+
results = g.enumerator(urls) do |url|
|
45
|
+
EventMachine::HttpRequest.new(url).get
|
46
|
+
end.map { |i| i.response }
|
47
|
+
p results
|
48
|
+
end
|
49
|
+
rescue Timeout::Error
|
50
|
+
p "Timeout!"
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
And much more soon ;)
|
55
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
|
47
|
+
task :default => :spec
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:spec) do |t|
|
50
|
+
t.libs << 'spec'
|
51
|
+
t.pattern = 'spec/**/*_spec.rb'
|
52
|
+
t.verbose = false
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Open an irb session preloaded with this library"
|
64
|
+
task :console do
|
65
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
#############################################################################
|
70
|
+
#
|
71
|
+
# Packaging tasks
|
72
|
+
#
|
73
|
+
#############################################################################
|
74
|
+
|
75
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
76
|
+
task :release => :build do
|
77
|
+
unless `git branch` =~ /^\* master$/
|
78
|
+
puts "You must be on the master branch to release!"
|
79
|
+
exit!
|
80
|
+
end
|
81
|
+
sh "git commit --allow-empty -m 'Release #{version}'"
|
82
|
+
sh "git tag v#{version}"
|
83
|
+
sh "git push origin master"
|
84
|
+
sh "git push origin v#{version}"
|
85
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Build #{gem_file} into the pkg directory"
|
89
|
+
task :build => :gemspec do
|
90
|
+
sh "mkdir -p pkg"
|
91
|
+
sh "gem build #{gemspec_file}"
|
92
|
+
sh "mv #{gem_file} pkg"
|
93
|
+
end
|
94
|
+
|
95
|
+
desc "Generate #{gemspec_file}"
|
96
|
+
task :gemspec => :validate do
|
97
|
+
# read spec file and split out manifest section
|
98
|
+
spec = File.read(gemspec_file)
|
99
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
100
|
+
|
101
|
+
# replace name version and date
|
102
|
+
replace_header(head, :name)
|
103
|
+
replace_header(head, :version)
|
104
|
+
replace_header(head, :date)
|
105
|
+
#comment this out if your rubyforge_project has a different name
|
106
|
+
replace_header(head, :rubyforge_project)
|
107
|
+
|
108
|
+
# determine file list from git ls-files
|
109
|
+
files = `git ls-files`.
|
110
|
+
split("\n").
|
111
|
+
sort.
|
112
|
+
reject { |file| file =~ /^\./ }.
|
113
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
114
|
+
map { |file| " #{file}" }.
|
115
|
+
join("\n")
|
116
|
+
|
117
|
+
# piece file back together and write
|
118
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
119
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
120
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
121
|
+
puts "Updated #{gemspec_file}"
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "Validate #{gemspec_file}"
|
125
|
+
task :validate do
|
126
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
127
|
+
unless Dir['VERSION*'].empty?
|
128
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
129
|
+
exit!
|
130
|
+
end
|
131
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
Cooperative multitasking for Ruby. Proof of concept.
|
2
|
+
|
3
|
+
Based on Ruby 1.9 Fibers, but unlike EM::Synchrony it uses symmetric coroutines (only #current and #transfer used) and HUB-orientend architecture. So coroutines transfer control to HUB and HUB transfer control to coroutines. Coroutines never tranfer control to each other.
|
4
|
+
|
5
|
+
In comparison with EM-Synchrony it allows:
|
6
|
+
- develop real complex cooperative multitasking apps;
|
7
|
+
- timeouts. Yes, in common case you cannot add timeout with EM-Synchrony;
|
8
|
+
- kill greens. And it safe unlike kill Threads;
|
9
|
+
- works with REPL and debugger. EM-Synchrony uses Fiber.yield, so you cannot run nothing in REPL;
|
10
|
+
- works with every environment. You can run nonblock web-applications with Unicorn;
|
11
|
+
- compatible with Ruby's Enumerator and with any other uses of Fibers themself (see https://github.com/igrigorik/em-synchrony/issues/114)
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'green'
|
15
|
+
require 'green/group'
|
16
|
+
require 'green-em/em-http'
|
17
|
+
|
18
|
+
g = Green::Pool.new(size: 2)
|
19
|
+
|
20
|
+
urls = ['http://google.com', 'http://yandex.ru']
|
21
|
+
|
22
|
+
results = g.enumerator(urls) do |url|
|
23
|
+
EventMachine::HttpRequest.new(url).get
|
24
|
+
end.map { |i| i.response }
|
25
|
+
|
26
|
+
p results
|
27
|
+
```
|
28
|
+
|
29
|
+
You can run it from Irb! ;)
|
30
|
+
|
31
|
+
You can add timeout:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'green'
|
35
|
+
require 'green/group'
|
36
|
+
require 'green-em/em-http'
|
37
|
+
|
38
|
+
g = Green::Pool.new(size: 2)
|
39
|
+
|
40
|
+
urls = ['http://google.com', 'http://yandex.ru']
|
41
|
+
|
42
|
+
begin
|
43
|
+
Green.timeout(1) do
|
44
|
+
results = g.enumerator(urls) do |url|
|
45
|
+
EventMachine::HttpRequest.new(url).get
|
46
|
+
end.map { |i| i.response }
|
47
|
+
p results
|
48
|
+
end
|
49
|
+
rescue Timeout::Error
|
50
|
+
p "Timeout!"
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
And much more soon ;)
|
55
|
+
|
data/app.ru
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'green'
|
6
|
+
require 'green/group'
|
7
|
+
|
8
|
+
app = proc do |env|
|
9
|
+
start = Time.now
|
10
|
+
g = Green::Group.new
|
11
|
+
results = []
|
12
|
+
g.spawn do
|
13
|
+
Green.sleep 1
|
14
|
+
results << :fiz
|
15
|
+
end
|
16
|
+
g.spawn do
|
17
|
+
Green.sleep 1
|
18
|
+
results << :buz
|
19
|
+
end
|
20
|
+
g.join
|
21
|
+
[200, {"Content-Type" => 'plain/text'}, ["Execution time: #{Time.now - start}; Results: #{results.inspect}"]]
|
22
|
+
end
|
23
|
+
|
24
|
+
run app
|
data/green.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
s.rubygems_version = '1.3.5'
|
5
|
+
|
6
|
+
s.name = 'green'
|
7
|
+
s.version = '0.0.1'
|
8
|
+
s.date = '2012-05-09'
|
9
|
+
s.rubyforge_project = 'green'
|
10
|
+
|
11
|
+
s.summary = "Cooperative multitasking fo Ruby"
|
12
|
+
s.description = "Cooperative multitasking fo Ruby"
|
13
|
+
|
14
|
+
s.authors = ["Andrew Rudenko"]
|
15
|
+
s.email = 'ceo@prepor.ru'
|
16
|
+
s.homepage = 'http://github.com/prepor/green'
|
17
|
+
|
18
|
+
s.require_paths = %w[lib]
|
19
|
+
|
20
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
21
|
+
s.extra_rdoc_files = %w[README.md]
|
22
|
+
|
23
|
+
|
24
|
+
# = MANIFEST =
|
25
|
+
s.files = %w[
|
26
|
+
Gemfile
|
27
|
+
Gemfile.lock
|
28
|
+
Rakefile
|
29
|
+
Readme.md
|
30
|
+
app.ru
|
31
|
+
green.gemspec
|
32
|
+
lib/green-em/em-http.rb
|
33
|
+
lib/green.rb
|
34
|
+
lib/green/event.rb
|
35
|
+
lib/green/ext.rb
|
36
|
+
lib/green/group.rb
|
37
|
+
lib/green/hub.rb
|
38
|
+
lib/green/hub/em.rb
|
39
|
+
lib/green/monkey.rb
|
40
|
+
lib/green/semaphore.rb
|
41
|
+
lib/green/tcp_socket.rb
|
42
|
+
]
|
43
|
+
# = MANIFEST =
|
44
|
+
|
45
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
46
|
+
## matches what you actually use.
|
47
|
+
s.test_files = s.files.select { |path| path =~ /^spec\/.*_spec\.rb/ }
|
48
|
+
end
|
data/lib/green/event.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Green
|
2
|
+
class Event
|
3
|
+
include Green::Waiter
|
4
|
+
attr_reader :waiters
|
5
|
+
def initialize
|
6
|
+
@waiters = []
|
7
|
+
@setted = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def set(result = nil)
|
11
|
+
@setted = true
|
12
|
+
@result = result
|
13
|
+
waiters.each { |v| Green.hub.callback { v.switch } }
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait
|
17
|
+
if @setted
|
18
|
+
@result
|
19
|
+
else
|
20
|
+
waiters << Green.current
|
21
|
+
Green.hub.wait self, Green.current
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def green_cancel(waiter)
|
26
|
+
waiters.delete waiter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/green/ext.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Fiber
|
2
|
+
|
3
|
+
# def self.new
|
4
|
+
# super.tap { |o| o[:green] = Green.current }
|
5
|
+
# end
|
6
|
+
|
7
|
+
#Attribute Reference--Returns the value of a fiber-local variable, using
|
8
|
+
#either a symbol or a string name. If the specified variable does not exist,
|
9
|
+
#returns nil.
|
10
|
+
def [](key)
|
11
|
+
local_fiber_variables[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
#Attribute Assignment--Sets or creates the value of a fiber-local variable,
|
15
|
+
#using either a symbol or a string. See also Fiber#[].
|
16
|
+
def []=(key,value)
|
17
|
+
local_fiber_variables[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def local_fiber_variables
|
23
|
+
@local_fiber_variables ||= {}
|
24
|
+
end
|
25
|
+
end
|
data/lib/green/group.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'green/event'
|
2
|
+
require 'green/semaphore'
|
3
|
+
class Green
|
4
|
+
class Group
|
5
|
+
attr_reader :options, :greens
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = options
|
8
|
+
@greens = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def spawn(*args, &blk)
|
12
|
+
g = Green.spawn do
|
13
|
+
blk.call(*args)
|
14
|
+
end
|
15
|
+
add g
|
16
|
+
g.callback { discard g }
|
17
|
+
g
|
18
|
+
end
|
19
|
+
|
20
|
+
def apply(&blk)
|
21
|
+
spawn(&blk).join
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(green)
|
25
|
+
greens << green
|
26
|
+
end
|
27
|
+
|
28
|
+
def discard(green)
|
29
|
+
greens.delete green
|
30
|
+
end
|
31
|
+
|
32
|
+
def join
|
33
|
+
while (g = greens.pop)
|
34
|
+
g.join
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def enumerator(iterable, &blk)
|
39
|
+
iter = iterable.each
|
40
|
+
Enumerator.new do |y|
|
41
|
+
e = Event.new
|
42
|
+
begin
|
43
|
+
waiting = 0
|
44
|
+
while true
|
45
|
+
i = iter.next
|
46
|
+
waiting += 1
|
47
|
+
spawn(i) do |item|
|
48
|
+
y << blk.call(item)
|
49
|
+
waiting -= 1
|
50
|
+
e.set if waiting == 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue StopIteration
|
54
|
+
e.wait
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Pool < Group
|
61
|
+
attr_reader :semaphore
|
62
|
+
def initialize(*args)
|
63
|
+
super
|
64
|
+
@semaphore = Semaphore.new(options[:size])
|
65
|
+
end
|
66
|
+
|
67
|
+
def spawn(*args, &blk)
|
68
|
+
semaphore.acquire
|
69
|
+
super() do
|
70
|
+
begin
|
71
|
+
blk.call(*args)
|
72
|
+
ensure
|
73
|
+
semaphore.release
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/green/hub/em.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
class ::EM::Timer
|
4
|
+
include Green::Waiter
|
5
|
+
|
6
|
+
def green_cancel
|
7
|
+
cancel
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ::EM::Deferrable
|
12
|
+
include Green::Waiter
|
13
|
+
|
14
|
+
def green_cancel
|
15
|
+
# instance_variable_get(:@callbacks).each { |c| cancel_callback c }
|
16
|
+
# instance_variable_get(:@errbacks).each { |c| cancel_errback c }
|
17
|
+
# cancel_timeout
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Green
|
22
|
+
class Hub
|
23
|
+
class EM < Hub
|
24
|
+
# если мы запускаем приложение внутри thin или rainbows с EM, то значит мы уже внутри EM-реактора, а hub должен переключиться в main тред.
|
25
|
+
def run
|
26
|
+
if ::EM.reactor_running?
|
27
|
+
loop do
|
28
|
+
Green.main.switch
|
29
|
+
end
|
30
|
+
else
|
31
|
+
::EM.run
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def timer(n, &blk)
|
36
|
+
::EM::Timer.new(n, &blk)
|
37
|
+
end
|
38
|
+
|
39
|
+
def callback(&blk)
|
40
|
+
::EM.next_tick(&blk)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/green/hub.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
class Green
|
2
|
+
class Hub
|
3
|
+
attr_reader :g
|
4
|
+
def initialize
|
5
|
+
c = Green.current
|
6
|
+
callback { c.switch }
|
7
|
+
@g = Green.new do
|
8
|
+
run
|
9
|
+
end
|
10
|
+
g.switch
|
11
|
+
end
|
12
|
+
|
13
|
+
def switch
|
14
|
+
g.switch
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait(waiter, *args)
|
18
|
+
switch
|
19
|
+
rescue => e
|
20
|
+
waiter.green_cancel(*args)
|
21
|
+
raise e
|
22
|
+
end
|
23
|
+
|
24
|
+
def sleep(n)
|
25
|
+
g = Green.current
|
26
|
+
t = timer(n) { g.switch }
|
27
|
+
wait t
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
raise "override"
|
32
|
+
end
|
33
|
+
|
34
|
+
def timer(n, &blk)
|
35
|
+
raise "override"
|
36
|
+
end
|
37
|
+
|
38
|
+
def callback(&blk)
|
39
|
+
raise "override"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/green/monkey.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class Green
|
2
|
+
class Semaphore
|
3
|
+
include Green::Waiter
|
4
|
+
attr_accessor :counter
|
5
|
+
def initialize(value = 1)
|
6
|
+
@counter = value
|
7
|
+
@links = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def acquire
|
11
|
+
if counter > 0
|
12
|
+
self.counter -= 1
|
13
|
+
true
|
14
|
+
else
|
15
|
+
g = Green.current
|
16
|
+
clb = rawlink { g.switch }
|
17
|
+
Green.hub.wait self, clb
|
18
|
+
self.counter -= 1
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def release
|
24
|
+
self.counter += 1
|
25
|
+
if @links.size > 0
|
26
|
+
l = @links.pop
|
27
|
+
Green.hub.callback { l.call }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def rawlink(&clb)
|
32
|
+
@links << clb
|
33
|
+
clb
|
34
|
+
end
|
35
|
+
|
36
|
+
def unlink(clb)
|
37
|
+
@links.delete clb
|
38
|
+
end
|
39
|
+
|
40
|
+
def green_cancel(clb)
|
41
|
+
unlink clb
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Green
|
2
|
+
class Socket < ::Socket
|
3
|
+
def accept
|
4
|
+
accept_nonblock
|
5
|
+
end
|
6
|
+
|
7
|
+
def connect(sock_addr)
|
8
|
+
connect_nonblock(sock_addr)
|
9
|
+
end
|
10
|
+
|
11
|
+
def send(mesg, flags = 0, dest_sockaddr = nil)
|
12
|
+
super(mesg, flags, dest_sockaddr)
|
13
|
+
rescue Errno::EAGAIN => e
|
14
|
+
wait_write
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
|
18
|
+
def recv(maxlen, flags = 0)
|
19
|
+
super(maxlen, flags = 0)
|
20
|
+
rescue Errno::EAGAIN => e
|
21
|
+
wait_read
|
22
|
+
retry
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
begin
|
2
|
+
require "em-http-request"
|
3
|
+
rescue LoadError => error
|
4
|
+
raise "Missing EM-Synchrony dependency: gem install em-http-request"
|
5
|
+
end
|
6
|
+
|
7
|
+
module EventMachine
|
8
|
+
class HTTPException < RuntimeError; end
|
9
|
+
module HTTPMethods
|
10
|
+
%w[get head post delete put].each do |type|
|
11
|
+
class_eval %[
|
12
|
+
alias :a#{type} :#{type}
|
13
|
+
def #{type}(options = {}, &blk)
|
14
|
+
g = Green.current
|
15
|
+
|
16
|
+
conn = setup_request(:#{type}, options, &blk)
|
17
|
+
if conn.error.nil?
|
18
|
+
conn.callback { g.switch(conn) }
|
19
|
+
conn.errback { g.throw(HTTPException.new(conn)) }
|
20
|
+
|
21
|
+
Green.hub.wait(conn)
|
22
|
+
else
|
23
|
+
raise HTTPException.new(conn)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/green.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
require 'pp'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
class Green
|
6
|
+
VERSION = "0.0.1"
|
7
|
+
class Proxy
|
8
|
+
attr_reader :f
|
9
|
+
def initialize
|
10
|
+
@f = Fiber.current
|
11
|
+
end
|
12
|
+
|
13
|
+
def switch(*args)
|
14
|
+
f.transfer(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Waiter
|
19
|
+
def green_cancel
|
20
|
+
raise "override"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
def thread_locals
|
27
|
+
@thread_locals ||= {}
|
28
|
+
@thread_locals[Thread.current] ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def sleep(n)
|
32
|
+
Green.hub.sleep(n)
|
33
|
+
end
|
34
|
+
|
35
|
+
def main
|
36
|
+
MAIN
|
37
|
+
end
|
38
|
+
|
39
|
+
def current
|
40
|
+
Fiber.current[:green] || Proxy.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def make_hub
|
44
|
+
Hub::EM.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def hub
|
48
|
+
# thread_locals[:hub] ||= make_hub
|
49
|
+
@hub ||= make_hub
|
50
|
+
end
|
51
|
+
|
52
|
+
def spawn(&blk)
|
53
|
+
Green.new(&blk).tap { |o| o.start }
|
54
|
+
end
|
55
|
+
|
56
|
+
def timeout(n, &blk)
|
57
|
+
g = current
|
58
|
+
timer = hub.timer(n) do
|
59
|
+
g.switch Timeout::Error.new
|
60
|
+
end
|
61
|
+
res = blk.call
|
62
|
+
timer.cancel
|
63
|
+
res
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# class GreenError < StandardError; end
|
68
|
+
# class GreenKill < GreenError; end
|
69
|
+
|
70
|
+
require 'green/ext'
|
71
|
+
require 'green/hub'
|
72
|
+
|
73
|
+
require 'green/hub/em'
|
74
|
+
|
75
|
+
attr_reader :f, :callbacks
|
76
|
+
def initialize()
|
77
|
+
@callbacks = []
|
78
|
+
@f = Fiber.new do
|
79
|
+
res = yield
|
80
|
+
@callbacks.each { |c| c.call(*res) }
|
81
|
+
Green.hub.switch
|
82
|
+
end
|
83
|
+
@f[:green] = self
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def switch(*args)
|
88
|
+
return unless f.alive?
|
89
|
+
f.transfer(*args).tap do |*res|
|
90
|
+
res.size == 1 && res.first.is_a?(Exception) && raise(res.first)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def throw(exc = RuntimeException.new)
|
95
|
+
switch(exc)
|
96
|
+
end
|
97
|
+
|
98
|
+
def start
|
99
|
+
Green.hub.callback { self.switch }
|
100
|
+
end
|
101
|
+
|
102
|
+
def callback(&blk)
|
103
|
+
callbacks << blk
|
104
|
+
end
|
105
|
+
|
106
|
+
def join
|
107
|
+
g = Green.current
|
108
|
+
callback { |*res| g.switch(*res) }
|
109
|
+
Green.hub.switch
|
110
|
+
end
|
111
|
+
|
112
|
+
# def kill
|
113
|
+
# self.throw(GreenKill.new)
|
114
|
+
# end
|
115
|
+
|
116
|
+
MAIN = Fiber.current[:green] = Proxy.new
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: green
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Rudenko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-09 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Cooperative multitasking fo Ruby
|
15
|
+
email: ceo@prepor.ru
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- README.md
|
20
|
+
files:
|
21
|
+
- Gemfile
|
22
|
+
- Gemfile.lock
|
23
|
+
- Rakefile
|
24
|
+
- Readme.md
|
25
|
+
- app.ru
|
26
|
+
- green.gemspec
|
27
|
+
- lib/green-em/em-http.rb
|
28
|
+
- lib/green.rb
|
29
|
+
- lib/green/event.rb
|
30
|
+
- lib/green/ext.rb
|
31
|
+
- lib/green/group.rb
|
32
|
+
- lib/green/hub.rb
|
33
|
+
- lib/green/hub/em.rb
|
34
|
+
- lib/green/monkey.rb
|
35
|
+
- lib/green/semaphore.rb
|
36
|
+
- lib/green/tcp_socket.rb
|
37
|
+
- README.md
|
38
|
+
homepage: http://github.com/prepor/green
|
39
|
+
licenses: []
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project: green
|
59
|
+
rubygems_version: 1.8.10
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: Cooperative multitasking fo Ruby
|
63
|
+
test_files: []
|
64
|
+
has_rdoc:
|