green 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "eventmachine", "~> 1.0.0.beta.4"
4
+ gem "em-http-request"
5
+ gem "unicorn"
6
+
7
+ gemspec
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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ class Green
2
+ module Monkey
3
+ extend self
4
+
5
+ def patch_constants
6
+
7
+ end
8
+
9
+ def patch_socket
10
+
11
+ end
12
+ end
13
+ end
@@ -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: