green 0.0.1 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
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