green 0.0.1 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,23 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "eventmachine", "~> 1.0.0.beta.4"
3
+ gem "eventmachine", "= 1.0.0.beta.4"
4
4
  gem "em-http-request"
5
+ gem "ffi-rzmq"
6
+ gem "mysql2"
5
7
  gem "unicorn"
8
+ gem "activerecord"
9
+ gem "algorithms"
10
+ gem "nio4r"
11
+ gem "rake"
12
+
13
+ group :test do
14
+ gem "rr"
15
+ gem "minitest"
16
+ gem "minitest-reporters"
17
+ end
18
+
19
+ group :development do
20
+ gem "debugger"
21
+ end
6
22
 
7
23
  gemspec
data/Gemfile.lock CHANGED
@@ -1,25 +1,68 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- green (0.0.1)
4
+ green (0.1)
5
+ kgio (= 2.7.4)
5
6
 
6
7
  GEM
7
8
  remote: http://rubygems.org/
8
9
  specs:
9
- addressable (2.2.7)
10
- em-http-request (1.0.0)
10
+ activemodel (3.2.3)
11
+ activesupport (= 3.2.3)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.3)
14
+ activemodel (= 3.2.3)
15
+ activesupport (= 3.2.3)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.3)
19
+ i18n (~> 0.6)
20
+ multi_json (~> 1.0)
21
+ addressable (2.2.8)
22
+ algorithms (0.5.0)
23
+ ansi (1.4.2)
24
+ arel (3.0.2)
25
+ builder (3.0.0)
26
+ columnize (0.3.5)
27
+ cookiejar (0.3.0)
28
+ debugger (1.1.3)
29
+ columnize (>= 0.3.1)
30
+ debugger-linecache (~> 1.1.1)
31
+ debugger-ruby_core_source (~> 1.1.2)
32
+ debugger-linecache (1.1.1)
33
+ debugger-ruby_core_source (>= 1.1.1)
34
+ debugger-ruby_core_source (1.1.2)
35
+ em-http-request (1.0.2)
11
36
  addressable (>= 2.2.3)
37
+ cookiejar
12
38
  em-socksify
13
- eventmachine (>= 1.0.0.beta.3)
14
- http_parser.rb (>= 0.5.2)
39
+ eventmachine (>= 1.0.0.beta.4)
40
+ http_parser.rb (>= 0.5.3)
15
41
  em-socksify (0.2.0)
16
42
  eventmachine (>= 1.0.0.beta.4)
17
- eventmachine (1.0.0.mail.7)
43
+ eventmachine (1.0.0.beta.4)
44
+ ffi (1.2.0)
45
+ ffi-rzmq (0.9.6)
46
+ ffi
18
47
  http_parser.rb (0.5.3)
48
+ i18n (0.6.0)
19
49
  kgio (2.7.4)
50
+ minitest (3.0.0)
51
+ minitest-reporters (0.7.1)
52
+ ansi
53
+ builder
54
+ minitest (>= 2.0, < 4.0)
55
+ ruby-progressbar
56
+ multi_json (1.3.6)
57
+ mysql2 (0.3.11)
58
+ nio4r (0.3.3)
20
59
  rack (1.4.1)
21
60
  raindrops (0.8.0)
22
- unicorn (4.2.1)
61
+ rake (0.9.2.2)
62
+ rr (1.0.4)
63
+ ruby-progressbar (0.0.10)
64
+ tzinfo (0.3.33)
65
+ unicorn (4.3.1)
23
66
  kgio (~> 2.6)
24
67
  rack
25
68
  raindrops (~> 0.7)
@@ -28,7 +71,17 @@ PLATFORMS
28
71
  ruby
29
72
 
30
73
  DEPENDENCIES
74
+ activerecord
75
+ algorithms
76
+ debugger
31
77
  em-http-request
32
- eventmachine (~> 1.0.0.beta.4)
78
+ eventmachine (= 1.0.0.beta.4)
79
+ ffi-rzmq
33
80
  green!
81
+ minitest
82
+ minitest-reporters
83
+ mysql2
84
+ nio4r
85
+ rake
86
+ rr
34
87
  unicorn
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ Green [![Build Status](https://secure.travis-ci.org/prepor/green.png)](http://travis-ci.org/prepor/green)
2
+ =====
3
+
1
4
  Cooperative multitasking for Ruby. Proof of concept.
2
5
 
3
6
  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.
@@ -51,5 +54,47 @@ rescue Timeout::Error
51
54
  end
52
55
  ```
53
56
 
54
- And much more soon ;)
57
+ You can use net/http (and any gem over it):
58
+
59
+ ```ruby
60
+ require 'green'
61
+ require 'green/group'
62
+ require 'green/monkey'
63
+ require 'net/http'
64
+
65
+ g = Green::Pool.new(size: 2)
66
+
67
+ hosts = ['google.com', 'yandex.ru']
68
+
69
+ results = g.enumerator(hosts) do |host|
70
+ Net::HTTP.get host, '/'
71
+ end.map { |i| i }
72
+
73
+ p results
74
+ ```
75
+
76
+ You can run nonblock Web application with any webserver:
77
+
78
+ ```ruby
79
+ require 'green'
80
+ require 'green/group'
81
+
82
+ app = proc do |env|
83
+ start = Time.now
84
+ g = Green::Group.new
85
+ results = []
86
+ g.spawn do
87
+ Green.sleep 1
88
+ results << :fiz
89
+ end
90
+ g.spawn do
91
+ Green.sleep 1
92
+ results << :buz
93
+ end
94
+ g.join
95
+ [200, {"Content-Type" => 'plain/text'}, ["Execution time: #{Time.now - start}; Results: #{results.inspect}"]]
96
+ end
97
+ run app
98
+ ```
55
99
 
100
+ Run it with `unicorn app.ru`
data/green.gemspec CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'green'
7
- s.version = '0.0.1'
8
- s.date = '2012-05-09'
7
+ s.version = '0.1'
8
+ s.date = '2012-12-13'
9
9
  s.rubyforge_project = 'green'
10
10
 
11
11
  s.summary = "Cooperative multitasking fo Ruby"
@@ -20,25 +20,44 @@ Gem::Specification.new do |s|
20
20
  s.rdoc_options = ["--charset=UTF-8"]
21
21
  s.extra_rdoc_files = %w[README.md]
22
22
 
23
-
23
+ s.add_runtime_dependency("kgio", "2.7.4")
24
24
  # = MANIFEST =
25
25
  s.files = %w[
26
26
  Gemfile
27
27
  Gemfile.lock
28
+ README.md
28
29
  Rakefile
29
- Readme.md
30
30
  app.ru
31
31
  green.gemspec
32
+ lib/active_record/connection_adapters/green_mysql2_adapter.rb
32
33
  lib/green-em/em-http.rb
33
34
  lib/green.rb
35
+ lib/green/activerecord.rb
36
+ lib/green/connection_pool.rb
34
37
  lib/green/event.rb
35
38
  lib/green/ext.rb
36
39
  lib/green/group.rb
37
40
  lib/green/hub.rb
38
41
  lib/green/hub/em.rb
42
+ lib/green/hub/nio4r.rb
39
43
  lib/green/monkey.rb
44
+ lib/green/mysql2.rb
40
45
  lib/green/semaphore.rb
41
- lib/green/tcp_socket.rb
46
+ lib/green/socket.rb
47
+ lib/green/zmq.rb
48
+ spec/green/activerecord_spec.rb
49
+ spec/green/connection_pool_spec.rb
50
+ spec/green/event_spec.rb
51
+ spec/green/group_spec.rb
52
+ spec/green/monkey_spec.rb
53
+ spec/green/mysql2_spec.rb
54
+ spec/green/semaphore_spec.rb
55
+ spec/green/socket_spec.rb
56
+ spec/green/tcpsocket_spec.rb
57
+ spec/green/zmq_spec.rb
58
+ spec/green_spec.rb
59
+ spec/helpers.rb
60
+ spec/spec_helper.rb
42
61
  ]
43
62
  # = MANIFEST =
44
63
 
@@ -0,0 +1,92 @@
1
+ # AR adapter for using a green mysql2 connection
2
+ # Just update your database.yml's adapter to be 'green_mysql2'
3
+
4
+ require 'green/mysql2'
5
+ require 'green/activerecord'
6
+ require 'active_record/connection_adapters/mysql2_adapter'
7
+
8
+ # module ActiveRecord
9
+ # class Base
10
+ # def self.green_mysql2_connection(config)
11
+ # client = Green::ActiveRecord::ConnectionPool.new(size: config[:pool]) do
12
+ # conn = ActiveRecord::ConnectionAdapters::GreenMysql2Adapter::Client.new(config.symbolize_keys)
13
+ # # From Mysql2Adapter#configure_connection
14
+ # conn.query_options.merge!(:as => :array)
15
+
16
+ # # By default, MySQL 'where id is null' selects the last inserted id.
17
+ # # Turn this off. http://dev.rubyonrails.org/ticket/6778
18
+ # variable_assignments = ['SQL_AUTO_IS_NULL=0']
19
+ # encoding = config[:encoding]
20
+ # variable_assignments << "NAMES '#{encoding}'" if encoding
21
+
22
+ # wait_timeout = config[:wait_timeout]
23
+ # wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
24
+ # variable_assignments << "@@wait_timeout = #{wait_timeout}"
25
+
26
+ # conn.query("SET #{variable_assignments.join(', ')}")
27
+ # conn
28
+ # end
29
+ # options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
30
+ # ActiveRecord::ConnectionAdapters::GreenMysql2Adapter.new(client, logger, options, config)
31
+ # end
32
+ # end
33
+
34
+ # module ConnectionAdapters
35
+ # class GreenMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter
36
+
37
+ # class Column < AbstractMysqlAdapter::Column # :nodoc:
38
+ # def adapter
39
+ # GreenMysql2Adapter
40
+ # end
41
+ # end
42
+
43
+ # ADAPTER_NAME = 'GreenMysql2'
44
+
45
+ # class Client < Green::Mysql2::Client
46
+ # include Green::ActiveRecord::Client
47
+ # end
48
+
49
+ # include Green::ActiveRecord::Adapter
50
+
51
+ # def connect
52
+
53
+ # end
54
+ # end
55
+ # end
56
+ # end
57
+
58
+
59
+
60
+ module ActiveRecord
61
+ class Base
62
+ def self.green_mysql2_connection(config)
63
+ config[:username] = 'root' if config[:username].nil?
64
+
65
+ if Mysql2::Client.const_defined? :FOUND_ROWS
66
+ config[:flags] = Mysql2::Client::FOUND_ROWS
67
+ end
68
+
69
+ client = Green::Mysql2::Client.new(config.symbolize_keys)
70
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
71
+ ConnectionAdapters::GreenMysql2Adapter.new(client, logger, options, config)
72
+ end
73
+ end
74
+
75
+ module ConnectionAdapters
76
+ class GreenMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter
77
+
78
+ class Column < AbstractMysqlAdapter::Column # :nodoc:
79
+ def adapter
80
+ GreenMysql2Adapter
81
+ end
82
+ end
83
+
84
+ ADAPTER_NAME = 'GreenMysql2'
85
+
86
+ def connect
87
+ @connection = Green::Mysql2::Client.new(@config)
88
+ configure_connection
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,21 @@
1
+ require 'green'
2
+ require 'active_record'
3
+ require 'active_record/connection_adapters/abstract/connection_pool'
4
+ require 'active_record/connection_adapters/abstract_adapter'
5
+ require 'green/semaphore'
6
+ require 'green/connection_pool'
7
+
8
+ suppress_warnings do
9
+ ::Thread = Green
10
+ ::Mutex = Green::Mutex
11
+ ::ConditionVariable = Green::ConditionVariable
12
+ end
13
+
14
+ class Green
15
+ class ActiveRecord < ::Green
16
+ def initialize
17
+ super
18
+ callback { ::ActiveRecord::Base.clear_active_connections! }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ class Green
2
+ class ConnectionPool
3
+ undef :send
4
+
5
+ def initialize(opts, &block)
6
+ @reserved = {} # map of in-progress connections
7
+ @available = [] # pool of free connections
8
+ @pending = [] # pending reservations (FIFO)
9
+
10
+ opts[:size].times do
11
+ @available.push(block.call) if block_given?
12
+ end
13
+ end
14
+
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
+ def execute
19
+ g = Green.current
20
+ begin
21
+ conn = acquire(g)
22
+ yield conn
23
+ 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
37
+ else
38
+ @pending.push green
39
+ Green.hub.wait { @pending.delete green }
40
+ acquire(green)
41
+ end
42
+ end
43
+
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)
50
+
51
+ if pending = @pending.shift
52
+ Green.hub.callback { pending.switch }
53
+ end
54
+ end
55
+
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
69
+ end
70
+ end
71
+
72
+ end
data/lib/green/event.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  class Green
2
2
  class Event
3
- include Green::Waiter
4
3
  attr_reader :waiters
5
4
  def initialize
6
5
  @waiters = []
@@ -17,13 +16,10 @@ class Green
17
16
  if @setted
18
17
  @result
19
18
  else
20
- waiters << Green.current
21
- Green.hub.wait self, Green.current
19
+ g = Green.current
20
+ waiters << g
21
+ Green.hub.wait { waiters.delete g }
22
22
  end
23
23
  end
24
-
25
- def green_cancel(waiter)
26
- waiters.delete waiter
27
- end
28
24
  end
29
25
  end
data/lib/green/ext.rb CHANGED
@@ -17,9 +17,17 @@ class Fiber
17
17
  local_fiber_variables[key] = value
18
18
  end
19
19
 
20
- private
21
-
22
20
  def local_fiber_variables
23
21
  @local_fiber_variables ||= {}
24
22
  end
23
+ end
24
+
25
+ module Kernel
26
+ def suppress_warnings
27
+ original_verbosity = $VERBOSE
28
+ $VERBOSE = nil
29
+ result = yield
30
+ $VERBOSE = original_verbosity
31
+ return result
32
+ end
25
33
  end
data/lib/green/group.rb CHANGED
@@ -3,13 +3,14 @@ require 'green/semaphore'
3
3
  class Green
4
4
  class Group
5
5
  attr_reader :options, :greens
6
- def initialize(options = {})
6
+ def initialize(options = {})
7
7
  @options = options
8
+ @options[:klass] ||= ::Green
8
9
  @greens = []
9
10
  end
10
11
 
11
12
  def spawn(*args, &blk)
12
- g = Green.spawn do
13
+ g = @options[:klass].spawn do
13
14
  blk.call(*args)
14
15
  end
15
16
  add g
@@ -30,11 +31,19 @@ class Green
30
31
  end
31
32
 
32
33
  def join
33
- while (g = greens.pop)
34
+ while (g = greens.first)
34
35
  g.join
35
36
  end
36
37
  end
37
38
 
39
+ def kill
40
+ greens.each(&:kill)
41
+ end
42
+
43
+ def size
44
+ greens.size
45
+ end
46
+
38
47
  def enumerator(iterable, &blk)
39
48
  iter = iterable.each
40
49
  Enumerator.new do |y|
@@ -47,9 +56,9 @@ class Green
47
56
  spawn(i) do |item|
48
57
  y << blk.call(item)
49
58
  waiting -= 1
50
- e.set if waiting == 0
59
+ e.set if waiting == 0
51
60
  end
52
- end
61
+ end
53
62
  rescue StopIteration
54
63
  e.wait
55
64
  end
@@ -61,6 +70,7 @@ class Green
61
70
  attr_reader :semaphore
62
71
  def initialize(*args)
63
72
  super
73
+ raise ArgumentError.new("Undefined option :size") unless options[:size]
64
74
  @semaphore = Semaphore.new(options[:size])
65
75
  end
66
76
 
@@ -74,5 +84,9 @@ class Green
74
84
  end
75
85
  end
76
86
  end
87
+
88
+ def join
89
+ semaphore.wait
90
+ end
77
91
  end
78
- end
92
+ end
data/lib/green/hub/em.rb CHANGED
@@ -1,26 +1,58 @@
1
1
  require 'eventmachine'
2
2
 
3
3
  class ::EM::Timer
4
- include Green::Waiter
5
-
6
4
  def green_cancel
7
5
  cancel
8
6
  end
9
7
  end
10
8
 
11
9
  module ::EM::Deferrable
12
- include Green::Waiter
13
-
14
10
  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
11
+ instance_variable_get(:@callbacks).each { |c| cancel_callback c }
12
+ instance_variable_get(:@errbacks).each { |c| cancel_errback c }
13
+ cancel_timeout
18
14
  end
19
15
  end
20
16
 
21
17
  class Green
22
18
  class Hub
23
19
  class EM < Hub
20
+ class SocketWaiter < Green::SocketWaiter
21
+ class Handler < ::EM::Connection
22
+ attr_accessor :green
23
+ def notify_readable
24
+ green.switch
25
+ end
26
+
27
+ def notify_writable
28
+ green.switch
29
+ end
30
+ end
31
+
32
+ def wait_read
33
+ make_handler(:readable)
34
+ end
35
+
36
+ def wait_write
37
+ make_handler(:writable)
38
+ end
39
+
40
+ def make_handler(mode)
41
+ h = ::EM.watch socket, Handler do |c|
42
+ c.green = Green.current
43
+ end
44
+ case mode
45
+ when :readable
46
+ h.notify_readable = true
47
+ when :writable
48
+ h.notify_writable = true
49
+ end
50
+ Green.hub.switch
51
+ ensure
52
+ h.detach
53
+ end
54
+
55
+ end
24
56
  # если мы запускаем приложение внутри thin или rainbows с EM, то значит мы уже внутри EM-реактора, а hub должен переключиться в main тред.
25
57
  def run
26
58
  if ::EM.reactor_running?
@@ -36,8 +68,16 @@ class Green
36
68
  ::EM::Timer.new(n, &blk)
37
69
  end
38
70
 
39
- def callback(&blk)
40
- ::EM.next_tick(&blk)
71
+ def callback(cb=nil, &blk)
72
+ ::EM.next_tick(cb || blk)
73
+ end
74
+
75
+ def socket_waiter(socket)
76
+ SocketWaiter.new socket
77
+ end
78
+
79
+ def stop
80
+ EM.stop
41
81
  end
42
82
  end
43
83
  end