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 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