green 0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- green (0.1)
4
+ green (0.1.1)
5
5
  kgio (= 2.7.4)
6
6
 
7
7
  GEM
@@ -23,15 +23,15 @@ GEM
23
23
  ansi (1.4.2)
24
24
  arel (3.0.2)
25
25
  builder (3.0.0)
26
- columnize (0.3.5)
26
+ columnize (0.3.6)
27
27
  cookiejar (0.3.0)
28
- debugger (1.1.3)
28
+ debugger (1.2.2)
29
29
  columnize (>= 0.3.1)
30
30
  debugger-linecache (~> 1.1.1)
31
- debugger-ruby_core_source (~> 1.1.2)
32
- debugger-linecache (1.1.1)
31
+ debugger-ruby_core_source (~> 1.1.5)
32
+ debugger-linecache (1.1.2)
33
33
  debugger-ruby_core_source (>= 1.1.1)
34
- debugger-ruby_core_source (1.1.2)
34
+ debugger-ruby_core_source (1.1.5)
35
35
  em-http-request (1.0.2)
36
36
  addressable (>= 2.2.3)
37
37
  cookiejar
@@ -4,8 +4,10 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'green'
7
- s.version = '0.1'
8
- s.date = '2012-12-13'
7
+
8
+ s.version = '0.1.1'
9
+ s.date = '2013-01-28'
10
+
9
11
  s.rubyforge_project = 'green'
10
12
 
11
13
  s.summary = "Cooperative multitasking fo Ruby"
@@ -30,7 +32,9 @@ Gem::Specification.new do |s|
30
32
  app.ru
31
33
  green.gemspec
32
34
  lib/active_record/connection_adapters/green_mysql2_adapter.rb
35
+ lib/green-em.rb
33
36
  lib/green-em/em-http.rb
37
+ lib/green-em/tarantool.rb
34
38
  lib/green.rb
35
39
  lib/green/activerecord.rb
36
40
  lib/green/connection_pool.rb
@@ -64,4 +68,4 @@ Gem::Specification.new do |s|
64
68
  ## Test files will be grabbed from the file list. Make sure the path glob
65
69
  ## matches what you actually use.
66
70
  s.test_files = s.files.select { |path| path =~ /^spec\/.*_spec\.rb/ }
67
- end
71
+ end
@@ -0,0 +1,24 @@
1
+ require 'green'
2
+ class Green
3
+ module EM
4
+ extend self
5
+ def sync(deferrable, params = {})
6
+ g = Green.current
7
+ deferrable.callback { |*args| g.switch(*get_args(args, params[:errback_args])) }
8
+ deferrable.errback { |*args| g.throw(get_args(args, params[:errback_args]).first) }
9
+ Green.hub.wait { deferrable.green_cancel }
10
+ end
11
+
12
+ def get_args(args, proc_args)
13
+ case proc_args
14
+ when Proc
15
+ proc.call(*args)
16
+ when nil
17
+ args
18
+ else
19
+ proc_args
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -3,6 +3,7 @@ begin
3
3
  rescue LoadError => error
4
4
  raise "Missing EM-Synchrony dependency: gem install em-http-request"
5
5
  end
6
+ require 'green-em'
6
7
 
7
8
  module EventMachine
8
9
  class HTTPException < RuntimeError; end
@@ -11,6 +12,7 @@ module EventMachine
11
12
  class_eval %[
12
13
  alias :a#{type} :#{type}
13
14
  def #{type}(options = {}, &blk)
15
+ <<<<<<< HEAD
14
16
  g = Green.current
15
17
  conn = setup_request(:#{type}, options, &blk)
16
18
  if conn.error.nil?
@@ -18,6 +20,11 @@ module EventMachine
18
20
  conn.errback { g.throw(HTTPException.new) }
19
21
 
20
22
  Green.hub.wait { conn.green_cancel }
23
+ =======
24
+ conn = setup_request(:"#{type}", options, &blk)
25
+ if conn.error.nil?
26
+ Green::EM.sync conn, callback_args: [conn], errback_args: [HTTPException.new(conn)]
27
+ >>>>>>> - Green::EM.sync
21
28
  else
22
29
  raise HTTPException.new
23
30
  end
@@ -25,4 +32,4 @@ module EventMachine
25
32
  ]
26
33
  end
27
34
  end
28
- end
35
+ end
File without changes
@@ -3,7 +3,8 @@ require 'pp'
3
3
  require 'thread'
4
4
 
5
5
  class Green
6
- VERSION = "0.1"
6
+ VERSION = "0.1.1"
7
+
7
8
 
8
9
  module GreenMethods
9
10
  def switch(*args)
@@ -33,6 +34,12 @@ class Green
33
34
  def []=(name, val)
34
35
  locals[name] = val
35
36
  end
37
+
38
+ def schedule(*args)
39
+ Green.hub.callback { self.switch(*args) }
40
+ end
41
+
42
+ alias call schedule
36
43
  end
37
44
 
38
45
  class Proxy
@@ -112,6 +119,10 @@ class Green
112
119
  def list
113
120
  list_hash.values
114
121
  end
122
+
123
+ def init
124
+ hub
125
+ end
115
126
  end
116
127
 
117
128
  class GreenError < StandardError; end
@@ -136,11 +147,9 @@ class Green
136
147
  begin
137
148
  res = yield
138
149
  rescue GreenKill => e
139
- rescue => e
140
- Green.main.throw e
141
150
  end
142
151
  @alive = false
143
- @callbacks.each { |c|
152
+ @callbacks.each { |c|
144
153
  c.call(res)
145
154
  }
146
155
  Green.list_hash.delete self
@@ -1,72 +1,74 @@
1
1
  class Green
2
2
  class ConnectionPool
3
- undef :send
3
+ class Proxy < BasicObject
4
+ def initialize(pool)
5
+ @pool = pool
6
+ end
7
+
8
+ def method_missing(method, *args, &blk)
9
+ @pool.execute do |conn|
10
+ conn.__send__(method, *args, &blk)
11
+ end
12
+ end
13
+ end
4
14
 
5
- def initialize(opts, &block)
6
- @reserved = {} # map of in-progress connections
15
+ attr_accessor :available, :pending, :disconnect_class, :new_block
16
+
17
+ def initialize(opts = {}, &block)
7
18
  @available = [] # pool of free connections
8
19
  @pending = [] # pending reservations (FIFO)
9
20
 
21
+ @disconnect_class = opts[:disconnect_class]
22
+ @new_block = block
10
23
  opts[:size].times do
11
- @available.push(block.call) if block_given?
24
+ @available.push(@new_block.call)
12
25
  end
13
26
  end
14
27
 
15
- # Choose first available connection and pass it to the supplied
16
- # block. This will block indefinitely until there is an available
17
- # connection to service the request.
18
28
  def execute
19
- g = Green.current
20
29
  begin
21
- conn = acquire(g)
30
+ conn = acquire
22
31
  yield conn
32
+ rescue => e
33
+ if @disconnect_class && e.is_a?(@disconnect_class)
34
+ disconnected = true
35
+ @available << @new_block.call
36
+ else
37
+ raise
38
+ end
23
39
  ensure
24
- release(g)
25
- end
26
- end
27
-
28
- private
29
-
30
- # Acquire a lock on a connection and assign it to executing fiber
31
- # - if connection is available, pass it back to the calling block
32
- # - if pool is full, yield the current fiber until connection is available
33
- def acquire(green)
34
- if conn = @available.pop
35
- @reserved[green.object_id] = conn
36
- conn
40
+ if disconnected
41
+ try_next
37
42
  else
38
- @pending.push green
39
- Green.hub.wait { @pending.delete green }
40
- acquire(green)
43
+ release conn
44
+ try_next
41
45
  end
42
46
  end
47
+ end
43
48
 
44
- # Release connection assigned to the supplied fiber and
45
- # resume any other pending connections (which will
46
- # immediately try to run acquire on the pool)
47
- def release(green)
48
- conn = @reserved.delete(green.object_id)
49
- @available.push(conn)
49
+ def proxy
50
+ @proxy ||= Proxy.new(self)
51
+ end
50
52
 
51
- if pending = @pending.shift
52
- Green.hub.callback { pending.switch }
53
- end
53
+ def acquire
54
+ g = Green.current
55
+ if conn = @available.pop
56
+ conn
57
+ else
58
+ @pending.push g
59
+ Green.hub.wait { @pending.delete g }
60
+ acquire
54
61
  end
62
+ end
55
63
 
56
- # Allow the pool to behave as the underlying connection
57
- #
58
- # If the requesting method begins with "a" prefix, then
59
- # hijack the callbacks and errbacks to fire a connection
60
- # pool release whenever the request is complete. Otherwise
61
- # yield the connection within execute method and release
62
- # once it is complete (assumption: fiber will yield until
63
- # data is available, or request is complete)
64
- #
65
- def method_missing(method, *args, &blk)
66
- execute do |conn|
67
- conn.__send__(method, *args, &blk)
68
- end
64
+ def release(conn)
65
+ @available.push conn
66
+ end
67
+
68
+ def try_next
69
+ if pending = @pending.shift
70
+ Green.hub.callback { pending.switch }
69
71
  end
72
+ end
70
73
  end
71
-
72
- end
74
+ end
@@ -2,14 +2,25 @@ class Green
2
2
  class Hub
3
3
  attr_reader :g
4
4
  def initialize
5
+
6
+ start_hub
7
+ end
8
+
9
+ def start_hub
5
10
  c = Green.current
6
- callback { c.switch }
11
+ callback { c.switch }
7
12
  @g = Green.new do
8
- run
13
+ begin
14
+ run
15
+ rescue => e
16
+ start_hub
17
+ raise
18
+ end
9
19
  end
10
20
  g.switch
11
21
  end
12
22
 
23
+
13
24
  def switch
14
25
  g.switch
15
26
  end
@@ -48,4 +59,4 @@ class Green
48
59
  raise "override"
49
60
  end
50
61
  end
51
- end
62
+ end
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'eventmachine'
2
3
 
3
4
  class ::EM::Timer
@@ -20,7 +21,7 @@ class Green
20
21
  class SocketWaiter < Green::SocketWaiter
21
22
  class Handler < ::EM::Connection
22
23
  attr_accessor :green
23
- def notify_readable
24
+ def notify_readable
24
25
  green.switch
25
26
  end
26
27
 
@@ -55,7 +56,7 @@ class Green
55
56
  end
56
57
  # если мы запускаем приложение внутри thin или rainbows с EM, то значит мы уже внутри EM-реактора, а hub должен переключиться в main тред.
57
58
  def run
58
- if ::EM.reactor_running?
59
+ if ::EM.reactor_running?
59
60
  loop do
60
61
  Green.main.switch
61
62
  end
@@ -81,4 +82,4 @@ class Green
81
82
  end
82
83
  end
83
84
  end
84
- end
85
+ end
@@ -1,6 +1,3 @@
1
- require 'nio'
2
- require 'algorithms'
3
-
4
1
  class Green
5
2
  class Hub
6
3
  class Nio4r < Hub
@@ -56,12 +53,15 @@ class Green
56
53
 
57
54
  attr_reader :callbacks, :timers, :cancel_timers
58
55
  def initialize(*args)
56
+ require 'nio'
57
+ require 'algorithms'
58
+
59
59
  @callbacks = []
60
60
  @timers = Containers::MinHeap.new
61
61
  @cancel_timers = {}
62
62
  super
63
63
  end
64
-
64
+
65
65
  def reactor_running?
66
66
  @reactor_running
67
67
  end
@@ -105,7 +105,7 @@ class Green
105
105
  i += 1
106
106
  end
107
107
  ensure
108
- @callbacks[0...0] = jobs[i..-1] if i < jobs.size
108
+ @callbacks[0...0] = jobs[(i + 1)..-1] if i < jobs.size
109
109
  end
110
110
  end
111
111
 
@@ -47,7 +47,7 @@ class Green
47
47
  return counter
48
48
  else
49
49
  g = Green.current
50
- clb = proc do
50
+ clb = proc do
51
51
  if counter >= v && @links.size == 0
52
52
  wait_links.delete clb
53
53
  Green.hub.callback { g.switch }
@@ -1,100 +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
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
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
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
@@ -8,8 +8,7 @@ QUERY = "select sleep(#{DELAY})"
8
8
 
9
9
  describe Green::ConnectionPool do
10
10
 
11
-
12
- let(:pool) { Green::ConnectionPool.new(size: size) { Green::Mysql2::Client.new } }
11
+ let(:pool) { Green::ConnectionPool.new(size: size) { Green::Mysql2::Client.new }.proxy }
13
12
 
14
13
  describe "pool size is exceeded" do
15
14
  let(:size) { 1 }
@@ -17,7 +16,7 @@ describe Green::ConnectionPool do
17
16
  start = Time.now.to_f
18
17
 
19
18
  g = Green::Group.new
20
- res = []
19
+ res = []
21
20
  g.spawn { res << pool.query(QUERY) }
22
21
  g.spawn { res << pool.query(QUERY) }
23
22
  g.join
@@ -42,4 +41,4 @@ describe Green::ConnectionPool do
42
41
  res.size.must_equal 2
43
42
  end
44
43
  end
45
- end
44
+ end
@@ -39,7 +39,8 @@ end
39
39
  describe Green::Pool do
40
40
  let(:e1) { Green::Event.new }
41
41
  let(:e2) { Green::Event.new }
42
- let(:p) { Green::Pool.new size: 1}
42
+ let(:size) { 1 }
43
+ let(:p) { Green::Pool.new size: size }
43
44
 
44
45
  it "should wait each task" do
45
46
  result = ""
@@ -49,6 +50,27 @@ describe Green::Pool do
49
50
  result.must_equal "fizbaz"
50
51
  end
51
52
 
53
+ describe "spawn attack" do
54
+ let(:size) { 10 }
55
+ it "should work correctly" do
56
+ i = 0
57
+ 100.times { p.spawn { i += 1 } }
58
+ p.join
59
+ i.must_equal 100
60
+ end
61
+
62
+ describe "with random timer" do
63
+ it "should work correctly" do
64
+ i = 0
65
+ 100.times { p.spawn { Green.sleep(rand); i += 1 } }
66
+ p.join
67
+ i.must_equal 100
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+
52
74
  # ugly test :(
53
75
  it "should block after limit" do
54
76
  i = 0
@@ -30,7 +30,7 @@ describe Green::Mysql2 do
30
30
  it "should fire simultaneous requests via pool" do
31
31
  db = Green::ConnectionPool.new(size: 2) do
32
32
  Green::Mysql2::Client.new
33
- end
33
+ end.proxy
34
34
 
35
35
  start = Time.now.to_f
36
36
  res = []
@@ -49,4 +49,4 @@ describe Green::Mysql2 do
49
49
  db.query("SELECT * FROM i_hope_this_table_does_not_exist;")
50
50
  }.must_raise(Mysql2::Error)
51
51
  end
52
- end
52
+ end
@@ -34,4 +34,11 @@ describe Green do
34
34
  (Time.now - t).must_be :<, 0.1
35
35
  end
36
36
  end
37
- end
37
+
38
+ describe "exceptions" do
39
+ it "should raise exception from reactor to main fiber" do
40
+ Green.hub.callback { raise "ololo" }
41
+ proc { Green.sleep(0.1) }.must_raise RuntimeError
42
+ end
43
+ end
44
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: green
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-13 00:00:00.000000000 Z
12
+ date: 2013-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: kgio
@@ -41,7 +41,9 @@ files:
41
41
  - app.ru
42
42
  - green.gemspec
43
43
  - lib/active_record/connection_adapters/green_mysql2_adapter.rb
44
+ - lib/green-em.rb
44
45
  - lib/green-em/em-http.rb
46
+ - lib/green-em/tarantool.rb
45
47
  - lib/green.rb
46
48
  - lib/green/activerecord.rb
47
49
  - lib/green/connection_pool.rb
@@ -84,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
86
  version: '0'
85
87
  segments:
86
88
  - 0
87
- hash: 1130764657698221389
89
+ hash: 4284378326440113504
88
90
  required_rubygems_version: !ruby/object:Gem::Requirement
89
91
  none: false
90
92
  requirements: