green 0.0.1 → 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/lib/green.rb CHANGED
@@ -3,26 +3,65 @@ require 'pp'
3
3
  require 'thread'
4
4
 
5
5
  class Green
6
- VERSION = "0.0.1"
6
+ VERSION = "0.1"
7
+
8
+ module GreenMethods
9
+ def switch(*args)
10
+ f.transfer(*args).tap do |*res|
11
+ if res.size == 1 && res.first.is_a?(ThrowException)
12
+ raise(res.first.exc)
13
+ end
14
+ end
15
+ end
16
+
17
+ def throw(exc = RuntimeException.new)
18
+ Green.hub.callback { switch(ThrowException.new exc) }
19
+ end
20
+
21
+ def kill
22
+ self.throw(GreenKill.new)
23
+ end
24
+
25
+ def locals
26
+ f.local_fiber_variables
27
+ end
28
+
29
+ def [](name)
30
+ locals[name]
31
+ end
32
+
33
+ def []=(name, val)
34
+ locals[name] = val
35
+ end
36
+ end
37
+
7
38
  class Proxy
39
+ include GreenMethods
8
40
  attr_reader :f
9
41
  def initialize
10
42
  @f = Fiber.current
11
43
  end
12
44
 
13
- def switch(*args)
14
- f.transfer(*args)
45
+ def alive?
46
+ f.alive?
15
47
  end
16
48
  end
17
49
 
18
- module Waiter
19
- def green_cancel
20
- raise "override"
50
+ class SocketWaiter
51
+ attr_reader :socket
52
+ def initialize(socket)
53
+ @socket = socket
54
+ end
55
+
56
+ def wait_read
57
+ end
58
+
59
+ def wait_write
21
60
  end
22
61
  end
23
62
 
24
63
  class << self
25
-
64
+
26
65
  def thread_locals
27
66
  @thread_locals ||= {}
28
67
  @thread_locals[Thread.current] ||= {}
@@ -41,7 +80,10 @@ class Green
41
80
  end
42
81
 
43
82
  def make_hub
44
- Hub::EM.new
83
+ hub_name = ENV['GREEN_HUB'] || 'Nio4r'
84
+ Hub.const_get(hub_name.to_sym).new
85
+ # Hub::Nio4r.new
86
+ # Hub::EM.new
45
87
  end
46
88
 
47
89
  def hub
@@ -50,68 +92,85 @@ class Green
50
92
  end
51
93
 
52
94
  def spawn(&blk)
53
- Green.new(&blk).tap { |o| o.start }
95
+ new(&blk).tap { |o| o.start }
54
96
  end
55
97
 
56
98
  def timeout(n, &blk)
57
99
  g = current
58
100
  timer = hub.timer(n) do
59
- g.switch Timeout::Error.new
101
+ g.throw Timeout::Error.new
60
102
  end
61
103
  res = blk.call
62
104
  timer.cancel
63
105
  res
64
106
  end
107
+
108
+ def list_hash
109
+ @list_hash ||= {}
110
+ end
111
+
112
+ def list
113
+ list_hash.values
114
+ end
65
115
  end
66
116
 
67
- # class GreenError < StandardError; end
68
- # class GreenKill < GreenError; end
117
+ class GreenError < StandardError; end
118
+ class GreenKill < GreenError; end
119
+
120
+ class ThrowException < Struct.new(:exc); end
69
121
 
70
122
  require 'green/ext'
71
123
  require 'green/hub'
72
124
 
73
125
  require 'green/hub/em'
126
+ require 'green/hub/nio4r'
127
+
128
+ include GreenMethods
74
129
 
75
130
  attr_reader :f, :callbacks
76
- def initialize()
131
+ def initialize
77
132
  @callbacks = []
133
+ @alive = true
134
+ Green.list_hash[self] = self
78
135
  @f = Fiber.new do
79
- res = yield
80
- @callbacks.each { |c| c.call(*res) }
136
+ begin
137
+ res = yield
138
+ rescue GreenKill => e
139
+ rescue => e
140
+ Green.main.throw e
141
+ end
142
+ @alive = false
143
+ @callbacks.each { |c|
144
+ c.call(res)
145
+ }
146
+ Green.list_hash.delete self
81
147
  Green.hub.switch
82
148
  end
83
149
  @f[:green] = self
84
150
  end
85
151
 
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)
152
+ def alive?
153
+ @alive
96
154
  end
97
155
 
98
156
  def start
99
157
  Green.hub.callback { self.switch }
100
158
  end
101
159
 
102
- def callback(&blk)
103
- callbacks << blk
160
+ def callback(cb=nil, &blk)
161
+ cb ||= blk
162
+ if alive?
163
+ callbacks << cb
164
+ else
165
+ Green.hub.callback(cb)
166
+ end
104
167
  end
105
168
 
106
169
  def join
107
170
  g = Green.current
108
- callback { |*res| g.switch(*res) }
171
+ callback { |res| Green.hub.callback { g.switch(res) } }
109
172
  Green.hub.switch
110
173
  end
111
174
 
112
- # def kill
113
- # self.throw(GreenKill.new)
114
- # end
115
-
116
175
  MAIN = Fiber.current[:green] = Proxy.new
117
- end
176
+ end
@@ -0,0 +1,100 @@
1
+ # DO NOT WORK
2
+
3
+ require "spec_helper"
4
+ require "green/activerecord"
5
+ require 'green/group'
6
+
7
+
8
+ # create database widgets;
9
+ # use widgets;
10
+ # create table widgets (
11
+ # id INT NOT NULL AUTO_INCREMENT,
12
+ # title varchar(255),
13
+ # PRIMARY KEY (`id`)
14
+ # );
15
+
16
+ class Widget < ActiveRecord::Base; end;
17
+
18
+ describe Green::ActiveRecord do
19
+ DELAY = 0.25
20
+ QUERY = "SELECT sleep(#{DELAY})"
21
+
22
+ def establish_connection
23
+ # ActiveRecord::Base.logger = Logger.new STDOUT
24
+ ActiveRecord::Base.establish_connection(
25
+ :adapter => 'green_mysql2',
26
+ :database => 'widgets',
27
+ :username => 'root',
28
+ :pool => 50
29
+ )
30
+ Widget.delete_all
31
+ end
32
+
33
+ before do
34
+ establish_connection
35
+ end
36
+
37
+ after do
38
+ ActiveRecord::Base.connection_pool.disconnect!
39
+ end
40
+
41
+ it "should establish AR connection" do
42
+ result = Widget.find_by_sql(QUERY)
43
+ result.size.must_equal 1
44
+ end
45
+
46
+ it "should fire sequential, synchronous requests within single green" do
47
+ start = Time.now.to_f
48
+ res = []
49
+
50
+ res.push Widget.find_by_sql(QUERY)
51
+ res.push Widget.find_by_sql(QUERY)
52
+
53
+ (Time.now.to_f - start.to_f).must_be_within_delta DELAY * res.size, DELAY * res.size * 0.15
54
+ res.size.must_equal 2
55
+ end
56
+
57
+ # it "should fire 100 requests" do
58
+ # pool = Green::Pool.new size: 40, klass: Green::ActiveRecord
59
+
60
+ # 100.times do
61
+ # pool.spawn do
62
+ # widget = Widget.create title: 'hi'
63
+ # widget.update_attributes title: 'hello'
64
+ # end
65
+ # end
66
+ # pool.join
67
+ # end
68
+
69
+ it "should create widget" do
70
+ Widget.create
71
+ Widget.create
72
+ Widget.count.must_equal 2
73
+ end
74
+
75
+ it "should update widget" do
76
+ widget = Widget.create title: 'hi'
77
+ widget.update_attributes title: 'hello'
78
+ Widget.find(widget.id).title.must_equal 'hello'
79
+ end
80
+
81
+ # describe "transactions" do
82
+ # it "should work properly" do
83
+ # pool = Green::Pool.new size: 40, klass: Green::ActiveRecord
84
+ # 50.times do |i|
85
+ # pool.spawn do
86
+ # widget = Widget.create title: "hello"
87
+ # ActiveRecord::Base.transaction do
88
+ # widget.update_attributes title: "hi#{i}"
89
+ # raise ActiveRecord::Rollback
90
+ # end
91
+ # end
92
+ # end
93
+ # pool.join
94
+ # Widget.all.each do |widget|
95
+ # widget.title.must_equal 'hello'
96
+ # end
97
+ # end
98
+ # end
99
+
100
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'green/connection_pool'
3
+ require 'green/mysql2'
4
+ require 'green/group'
5
+
6
+ DELAY = 0.25
7
+ QUERY = "select sleep(#{DELAY})"
8
+
9
+ describe Green::ConnectionPool do
10
+
11
+
12
+ let(:pool) { Green::ConnectionPool.new(size: size) { Green::Mysql2::Client.new } }
13
+
14
+ describe "pool size is exceeded" do
15
+ let(:size) { 1 }
16
+ it "should queue requests" do
17
+ start = Time.now.to_f
18
+
19
+ g = Green::Group.new
20
+ res = []
21
+ g.spawn { res << pool.query(QUERY) }
22
+ g.spawn { res << pool.query(QUERY) }
23
+ g.join
24
+
25
+ (Time.now.to_f - start.to_f).must_be_within_delta DELAY * 2, DELAY * 2 * 0.15
26
+ res.size.must_equal 2
27
+ end
28
+ end
29
+
30
+ describe "pool size is enough" do
31
+ let(:size) { 2 }
32
+ it "should parallel requests" do
33
+ start = Time.now.to_f
34
+
35
+ g = Green::Group.new
36
+ res = []
37
+ g.spawn { res << pool.query(QUERY) }
38
+ g.spawn { res << pool.query(QUERY) }
39
+ g.join
40
+
41
+ (Time.now.to_f - start.to_f).must_be_within_delta DELAY, DELAY * 0.30
42
+ res.size.must_equal 2
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'green/event'
3
+ require 'green/group'
4
+ describe Green::Event do
5
+ let(:e) { Green::Event.new }
6
+ let(:g) { Green::Group.new }
7
+ it "should wakeup all greens" do
8
+ i = 0
9
+ 5.times do
10
+ g.spawn { e.wait; i += 1}
11
+ end
12
+ i.must_equal 0
13
+ e.set
14
+ g.join
15
+ i.must_equal 5
16
+ end
17
+
18
+ describe "set before wait" do
19
+ it "should work properly" do
20
+ e.set(:ok)
21
+ e.wait.must_equal :ok
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require 'green/group'
3
+ require 'green/event'
4
+ require 'set'
5
+ describe Green::Group do
6
+ let(:g) { Green::Group.new }
7
+
8
+ describe "join" do
9
+ it "should wait each task" do
10
+ result = ""
11
+ g.spawn { result << "fiz" }
12
+ g.spawn { result << "baz" }
13
+ g.join
14
+ result.must_equal "fizbaz"
15
+ end
16
+
17
+ # it "return each result" do
18
+ # g.spawn { 1 }
19
+ # g.spawn { 2 }
20
+ # g.join.reduce(0, &:+).must_equal 3
21
+ # end
22
+ end
23
+
24
+ describe "apply" do
25
+ it "should wait result" do
26
+ g.apply { :foo }.must_equal :foo
27
+ end
28
+ end
29
+
30
+ describe "enumerator" do
31
+ it "should return enumerator" do
32
+ en = g.enumerator([1, 2, 3]) { |o| o }
33
+ en.must_be_instance_of Enumerator
34
+ en.map { |o| o }.must_equal [1, 2, 3]
35
+ end
36
+ end
37
+ end
38
+
39
+ describe Green::Pool do
40
+ let(:e1) { Green::Event.new }
41
+ let(:e2) { Green::Event.new }
42
+ let(:p) { Green::Pool.new size: 1}
43
+
44
+ it "should wait each task" do
45
+ result = ""
46
+ p.spawn { result << "fiz"; Green.sleep(0) }
47
+ Green.spawn { p.spawn { result << "baz"; Green.sleep(0) } }
48
+ p.join
49
+ result.must_equal "fizbaz"
50
+ end
51
+
52
+ # ugly test :(
53
+ it "should block after limit" do
54
+ i = 0
55
+ p.spawn { i += 1; e1.set; e2.wait }
56
+ Green.spawn { p.spawn { i += 1 } }
57
+ p.size.must_equal 1
58
+ e1.wait
59
+ i.must_equal 1
60
+ Green.sleep(0)
61
+ i.must_equal 1
62
+ p.size.must_equal 1
63
+ e2.set
64
+ p.join
65
+ i.must_equal 2
66
+ end
67
+ end
68
+
@@ -0,0 +1,22 @@
1
+ # require 'spec_helper'
2
+ # require 'green/monkey'
3
+
4
+ # describe "green/monkey" do
5
+ # let(:server) { TCPServer.new("127.0.0.1", 2225) }
6
+ # let(:client) { TCPSocket.open("127.0.0.1", 2225) }
7
+
8
+ # after { server.close; client.close }
9
+ # it "should read and write" do
10
+ # Green.spawn do
11
+ # server.listen(1)
12
+ # c, a = server.accept
13
+ # c.write "hello"
14
+ # c.close
15
+ # end
16
+
17
+ # g = Green.spawn do
18
+ # client.read.must_equal "hello"
19
+ # end
20
+ # g.join
21
+ # end
22
+ # end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+ require "green/mysql2"
3
+ require "green/connection_pool"
4
+ require "green/group"
5
+
6
+ describe Green::Mysql2 do
7
+
8
+ DELAY = 0.25
9
+ QUERY = "SELECT sleep(#{DELAY})"
10
+
11
+ it "should support queries" do
12
+ res = []
13
+ db = Green::Mysql2::Client.new
14
+ res = db.query QUERY
15
+
16
+ res.first.keys.must_include "sleep(#{DELAY})"
17
+ end
18
+
19
+ it "should fire sequential, synchronous requests" do
20
+ db = Green::Mysql2::Client.new
21
+
22
+ start = Time.now.to_f
23
+ res = []
24
+
25
+ res.push db.query QUERY
26
+ res.push db.query QUERY
27
+ (Time.now.to_f - start.to_f).must_be_within_delta DELAY * res.size, DELAY * res.size * 0.15
28
+ end
29
+
30
+ it "should fire simultaneous requests via pool" do
31
+ db = Green::ConnectionPool.new(size: 2) do
32
+ Green::Mysql2::Client.new
33
+ end
34
+
35
+ start = Time.now.to_f
36
+ res = []
37
+ g = Green::Group.new
38
+ g.spawn { res << db.query(QUERY) }
39
+ g.spawn { res << db.query(QUERY) }
40
+ g.join
41
+
42
+ (Time.now.to_f - start.to_f).must_be_within_delta DELAY, DELAY * 0.30
43
+ res.size.must_equal 2
44
+ end
45
+
46
+ it "should raise Mysql::Error in case of error" do
47
+ db = Green::Mysql2::Client.new
48
+ proc {
49
+ db.query("SELECT * FROM i_hope_this_table_does_not_exist;")
50
+ }.must_raise(Mysql2::Error)
51
+ end
52
+ end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+ require 'green/semaphore'
3
+ require 'green/event'
4
+ require 'green/group'
5
+
6
+ describe Green::Mutex do
7
+ let(:m) { Green::Mutex.new }
8
+ it "should synchronize" do
9
+ e = Green::Event.new
10
+ i = 0
11
+ Green.spawn do
12
+ m.synchronize do
13
+ e.wait
14
+ i += 1
15
+ end
16
+ end
17
+ Green.spawn do
18
+ e.set
19
+ m.synchronize do
20
+ i.must_equal 1
21
+ end
22
+ end.join
23
+ end
24
+
25
+ describe "lock" do
26
+ describe "when mutex already locked" do
27
+ it "should raise GreenError" do
28
+ proc {
29
+ m.lock
30
+ m.lock
31
+ }.must_raise Green::GreenError
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "sleep" do
37
+ describe "without timeout" do
38
+ it "should sleep until switch" do
39
+ m.lock
40
+ i = 0
41
+ g = Green.current
42
+ Green.hub.callback { i += 1; g.switch }
43
+ res = m.sleep
44
+ i.must_equal 1
45
+ end
46
+
47
+ it "should release lock" do
48
+ i = 0
49
+ e = Green::Event.new
50
+ group = Green::Group.new
51
+ g1 = group.spawn do
52
+ m.lock
53
+ e.wait
54
+ i += 1
55
+ m.sleep
56
+ end
57
+ group.spawn do
58
+ e.set
59
+ m.lock
60
+ i.must_equal 1
61
+ m.unlock
62
+ Green.hub.callback { g1.switch }
63
+ end
64
+ group.join
65
+ end
66
+
67
+ it "should wait unlock after switch" do
68
+ i = 0
69
+ group = Green::Group.new
70
+ g1 = group.spawn do
71
+ m.lock
72
+ m.sleep
73
+ i.must_equal 1
74
+ end
75
+ group.spawn do
76
+ m.lock
77
+ i += 1
78
+ m.unlock
79
+ Green.hub.callback { g1.switch }
80
+ end
81
+ group.join
82
+ end
83
+ end
84
+
85
+ describe "with timeout" do
86
+ it "should sleep for timeout" do
87
+ m.lock
88
+ i = 0
89
+ Green.hub.callback { i += 1 }
90
+ m.sleep(0.1)
91
+ i.must_equal 1
92
+ end
93
+ describe "and resume before timeout" do
94
+ it "should not raise any execptions" do
95
+ m.lock
96
+ g = Green.current
97
+ Green.hub.callback { g.switch }
98
+ m.sleep(0.05)
99
+ Green.spawn { Green.sleep(0.1) }.join
100
+ end
101
+ it "should resume in nested Green" do
102
+ Green.spawn do
103
+ m.synchronize do
104
+ t = m.sleep(0.05)
105
+ t.must_be :>=, 0.05
106
+ end
107
+ end.join
108
+ end
109
+ end
110
+ end
111
+ end
112
+ describe Green::ConditionVariable do
113
+ let(:c){ Green::ConditionVariable.new }
114
+ it "should wakeup waiter" do
115
+ i = ''
116
+ g = Green::Group.new
117
+ g.spawn do
118
+ m.synchronize do
119
+ i << 'a'
120
+ c.wait(m)
121
+ i << 'c'
122
+ end
123
+ end
124
+ g.spawn do
125
+ i << 'b'
126
+ c.signal
127
+ end
128
+ g.join
129
+ i.must_equal 'abc'
130
+ end
131
+
132
+ it 'should allow to play ping-pong' do
133
+ i = ''
134
+ g = Green::Group.new
135
+ g.spawn do
136
+ m.synchronize do
137
+ i << 'pi'
138
+ c.wait(m)
139
+ i << '-po'
140
+ c.signal
141
+ end
142
+ end
143
+ g.spawn do
144
+ m.synchronize do
145
+ i << 'ng'
146
+ c.signal
147
+ c.wait(m)
148
+ i << 'ng'
149
+ end
150
+ end
151
+ g.join
152
+ i.must_equal 'ping-pong'
153
+ end
154
+ it 'should not raise, when timer wakes up green between `signal` and `next_tick`' do
155
+ e = Green::Event.new
156
+ g = Green.spawn do
157
+ m.synchronize do
158
+ c.wait(m, 0.0001)
159
+ end
160
+ end
161
+ Green.hub.callback do
162
+ c.signal
163
+ Green.hub.callback { e.set }
164
+ end
165
+ e.wait
166
+ g.join
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'green/socket'
3
+ describe Green::Socket do
4
+ let(:s1) { Green::Socket.new(:INET, :STREAM, 0).tap { |s| s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) } }
5
+ let(:s2) { Green::Socket.new(:INET, :STREAM, 0) }
6
+
7
+ after do
8
+ s1.close
9
+ s2.close
10
+ end
11
+ it "should read and write" do
12
+ Green.spawn do
13
+ s1.bind(Addrinfo.tcp("127.0.0.1", 2225))
14
+ s1.listen(1)
15
+ c, a = s1.accept
16
+ c.write "hello"
17
+ c.close
18
+ end
19
+
20
+ g = Green.spawn do
21
+ s2.connect(Addrinfo.tcp("127.0.0.1", 2225))
22
+ s2.read.must_equal "hello"
23
+ end
24
+ g.join
25
+ end
26
+ end