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/Gemfile +17 -1
- data/Gemfile.lock +61 -8
- data/README.md +46 -1
- data/green.gemspec +24 -5
- data/lib/active_record/connection_adapters/green_mysql2_adapter.rb +92 -0
- data/lib/green/activerecord.rb +21 -0
- data/lib/green/connection_pool.rb +72 -0
- data/lib/green/event.rb +3 -7
- data/lib/green/ext.rb +10 -2
- data/lib/green/group.rb +20 -6
- data/lib/green/hub/em.rb +49 -9
- data/lib/green/hub/nio4r.rb +147 -0
- data/lib/green/hub.rb +12 -3
- data/lib/green/monkey.rb +11 -13
- data/lib/green/mysql2.rb +16 -0
- data/lib/green/semaphore.rb +136 -6
- data/lib/green/socket.rb +124 -0
- data/lib/green/zmq.rb +92 -0
- data/lib/green-em/em-http.rb +3 -4
- data/lib/green.rb +92 -33
- data/spec/green/activerecord_spec.rb +100 -0
- data/spec/green/connection_pool_spec.rb +45 -0
- data/spec/green/event_spec.rb +24 -0
- data/spec/green/group_spec.rb +68 -0
- data/spec/green/monkey_spec.rb +22 -0
- data/spec/green/mysql2_spec.rb +52 -0
- data/spec/green/semaphore_spec.rb +169 -0
- data/spec/green/socket_spec.rb +26 -0
- data/spec/green/tcpsocket_spec.rb +417 -0
- data/spec/green/zmq_spec.rb +121 -0
- data/spec/green_spec.rb +37 -0
- data/spec/helpers.rb +0 -0
- data/spec/spec_helper.rb +32 -0
- metadata +56 -9
- data/Readme.md +0 -55
- data/lib/green/tcp_socket.rb +0 -25
data/lib/green.rb
CHANGED
@@ -3,26 +3,65 @@ require 'pp'
|
|
3
3
|
require 'thread'
|
4
4
|
|
5
5
|
class Green
|
6
|
-
VERSION = "0.
|
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
|
14
|
-
f.
|
45
|
+
def alive?
|
46
|
+
f.alive?
|
15
47
|
end
|
16
48
|
end
|
17
49
|
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
68
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
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 {
|
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
|