oldmoe-neverblock 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +21 -0
- data/lib/never_block.rb +15 -0
- data/lib/never_block/extensions/fiber_extensions.rb +25 -0
- data/lib/never_block/frameworks/activerecord.rb +31 -0
- data/lib/never_block/frameworks/rails.rb +21 -0
- data/lib/never_block/pool/fiber_pool.rb +76 -0
- data/lib/never_block/pool/fibered_connection_pool.rb +114 -0
- data/lib/never_block/servers/thin.rb +34 -0
- data/lib/neverblock.rb +8 -0
- data/neverblock.gemspec +25 -0
- metadata +64 -0
data/README
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== NeverBlock
|
2
|
+
Never, ever!
|
3
|
+
|
4
|
+
NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
|
5
|
+
|
6
|
+
NeverBlock currently provides the following Libraries:
|
7
|
+
|
8
|
+
=== FiberExtensions
|
9
|
+
A set of extenstions to the standard Fiber implementation
|
10
|
+
|
11
|
+
=== NeverBlock::Pool::FiberPool
|
12
|
+
A pool of fibers that can be used to provide an upper limit to the numbers of active fibers in an application
|
13
|
+
|
14
|
+
=== NeverBlock::Pool::FiberedConnectionPool
|
15
|
+
A generic fibered connection pool for all sorts of connections with support for transactions. This was mostly copied from Sequel::ConnectionPool
|
16
|
+
|
17
|
+
NeverBlock should be the basis for providing completely async Ruby application development that does not require the usual twisted style of evented programming. For example, you will be able to develop in Rails in the usual style and deploy to a NeverBlock server which will do all the IO in an evented manner without you even noticing this.
|
18
|
+
|
19
|
+
=== License
|
20
|
+
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
|
21
|
+
|
data/lib/never_block.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require 'fiber'
|
8
|
+
require 'never_block/extensions/fiber_extensions'
|
9
|
+
require 'never_block/pool/fiber_pool'
|
10
|
+
require 'never_block/pool/fibered_connection_pool'
|
11
|
+
|
12
|
+
module NeverBlock
|
13
|
+
end
|
14
|
+
|
15
|
+
NB = NeverBlock
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
require 'fiber'
|
6
|
+
|
7
|
+
class Fiber
|
8
|
+
|
9
|
+
#Attribute Reference--Returns the value of a fiber-local variable, using either a symbol or a string name. If the specified variable does not exist, returns nil.
|
10
|
+
def [](key)
|
11
|
+
local_fiber_variables[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
#Attribute Assignment--Sets or creates the value of a fiber-local variable, using either a symbol or a string. See also Fiber#[].
|
15
|
+
def []=(key,value)
|
16
|
+
local_fiber_variables[key] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def local_fiber_variables
|
22
|
+
@local_fiber_variables ||= {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'never_block/frameworks/rails'
|
2
|
+
require 'activerecord'
|
3
|
+
|
4
|
+
# Patch ActiveRecord to store transaction depth information
|
5
|
+
# in fibers instead of threads. AR does not support nested
|
6
|
+
# transactions which makes the job easy.
|
7
|
+
class ActiveRecord::Base
|
8
|
+
|
9
|
+
def self.transaction(&block)
|
10
|
+
increment_open_transactions
|
11
|
+
begin
|
12
|
+
connection.transaction(Fiber.current['start_db_transaction'], &block)
|
13
|
+
ensure
|
14
|
+
decrement_open_transactions
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.increment_open_transactions #:nodoc:
|
21
|
+
open = Fiber.current['open_transactions'] ||= 0
|
22
|
+
Fiber.current['start_db_transaction'] = open.zero?
|
23
|
+
Fiber.current['open_transactions'] = open + 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.decrement_open_transactions #:nodoc:
|
27
|
+
Fiber.current['open_transactions'] -= 1
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'actionpack'
|
2
|
+
|
3
|
+
# Rails tries to protect dispatched actions
|
4
|
+
# by wrapping them in a synchronized code
|
5
|
+
# block, since fibers hate synchronized
|
6
|
+
# blocks we will trick the guard and
|
7
|
+
# transform it (without it knowing) to
|
8
|
+
# something more subtle
|
9
|
+
|
10
|
+
class ActionController::Dispatcher
|
11
|
+
|
12
|
+
# let's show this guard who is
|
13
|
+
# the man of the house
|
14
|
+
@@guard = Object.new
|
15
|
+
|
16
|
+
# now you synchronize
|
17
|
+
def @@guard.synchronize(&block)
|
18
|
+
# now you don't!
|
19
|
+
block.call
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# we need Fiber.current
|
2
|
+
# so we must require
|
3
|
+
# fiber
|
4
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
5
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
6
|
+
# License:: Distributes under the same terms as Ruby
|
7
|
+
|
8
|
+
require 'fiber'
|
9
|
+
|
10
|
+
module NeverBlock
|
11
|
+
module Pool
|
12
|
+
|
13
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
14
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
15
|
+
# License:: Distributes under the same terms as Ruby
|
16
|
+
#
|
17
|
+
# A pool of initialized fibers
|
18
|
+
# It does not grow in size or create transient fibers
|
19
|
+
# It will queue code blocks when needed (if all its fibers are busy)
|
20
|
+
#
|
21
|
+
# This class is particulary useful when you use the fibers
|
22
|
+
# to connect to evented back ends. It also does not generate
|
23
|
+
# transient objects and thus saves memory.
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
# fiber_pool = NeverBlock::Pool::FiberPool.new(150)
|
27
|
+
#
|
28
|
+
# loop do
|
29
|
+
# fiber_pool.spawn do
|
30
|
+
# #fiber body goes here
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class FiberPool
|
35
|
+
|
36
|
+
# gives access to the currently free fibers
|
37
|
+
attr_reader :fibers
|
38
|
+
|
39
|
+
# Prepare a list of fibers
|
40
|
+
# that are able to run different
|
41
|
+
# blocks of code every time
|
42
|
+
# once a fiber is done with its block
|
43
|
+
# it attempts to fetch another one
|
44
|
+
# from the queue.
|
45
|
+
def initialize(count = 50)
|
46
|
+
@fibers,@queue = [],[]
|
47
|
+
count.times do |i|
|
48
|
+
fiber = Fiber.new do |block|
|
49
|
+
loop do
|
50
|
+
block.call
|
51
|
+
unless @queue.empty?
|
52
|
+
block = @queue.shift
|
53
|
+
else
|
54
|
+
block = Fiber.yield @fibers << Fiber.current
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
fiber[:neverblock] = true
|
59
|
+
@fibers << fiber
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# If there is an available fiber
|
64
|
+
# use it, otherwise, leave it to linger in a queue
|
65
|
+
def spawn(evented = true, &block)
|
66
|
+
if fiber = @fibers.shift
|
67
|
+
fiber[:evented] = evented
|
68
|
+
fiber.resume(block)
|
69
|
+
else
|
70
|
+
@queue << block
|
71
|
+
end
|
72
|
+
self # we are keen on hiding our queue
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
module NeverBlock
|
6
|
+
module Pool
|
7
|
+
|
8
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
9
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
10
|
+
# License:: Distributes under the same terms as Ruby
|
11
|
+
#
|
12
|
+
# This class represents a pool of connections,
|
13
|
+
# you hold or release conncetions from the pool
|
14
|
+
# hold requests that cannot be fullfiled will be queued
|
15
|
+
# the fiber will be paused and resumed later when
|
16
|
+
# a connection is avaialble
|
17
|
+
#
|
18
|
+
# Large portions of this class were copied and pasted
|
19
|
+
# form Sequel's threaded connection pool
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# pool = NeverBlock::Pool::FiberedConnectionPool.new(:size=>16)do
|
24
|
+
# # connection creation code goes here
|
25
|
+
# end
|
26
|
+
# 32.times do
|
27
|
+
# Fiber.new do
|
28
|
+
# conn = pool.hold # hold will pause the fiber until a connection is available
|
29
|
+
# conn.execute('something') # you can use the connection normally now
|
30
|
+
# end.resume
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# The pool has support for transactions, just pass true to the pool#hold method
|
34
|
+
# and the connection will not be released after the block is finished
|
35
|
+
# It is the responsibility of client code to release the connection
|
36
|
+
class FiberedConnectionPool
|
37
|
+
|
38
|
+
# initialize the connection pool
|
39
|
+
# using the supplied proc to create the connections
|
40
|
+
# you can choose to start them eagerly or lazily (lazy by default)
|
41
|
+
def initialize(options = {}, &block)
|
42
|
+
@connections, @busy_connections, @queue = [], {},[]
|
43
|
+
@connection_proc = block
|
44
|
+
@size = options[:size] || 8
|
45
|
+
if options[:eager]
|
46
|
+
@size.times do
|
47
|
+
@connections << @connection_proc.call
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# If a connection is available,
|
53
|
+
# pass it to the block, otherwise
|
54
|
+
# pass the fiber to the queue
|
55
|
+
# till a connection is available
|
56
|
+
# when done with a connection
|
57
|
+
# try to porcess other fibers in the queue
|
58
|
+
# before releasing the connection
|
59
|
+
# if inside a transaction, don't release the fiber
|
60
|
+
def hold(transactional = false)
|
61
|
+
fiber = Fiber.current
|
62
|
+
if conn = @busy_connections[fiber]
|
63
|
+
return yield(conn)
|
64
|
+
end
|
65
|
+
conn = acquire(fiber)
|
66
|
+
begin
|
67
|
+
yield conn
|
68
|
+
ensure
|
69
|
+
release(fiber, conn) unless transactional
|
70
|
+
process_queue
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Give the fiber back to the pool
|
75
|
+
# you have to call this explicitly if
|
76
|
+
# you held a connection for a transaction
|
77
|
+
def release(fiber, conn)
|
78
|
+
@busy_connections.delete(fiber)
|
79
|
+
@connections << conn
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Can we find a connection?
|
85
|
+
# Can we create one?
|
86
|
+
# Wait in the queue then
|
87
|
+
def acquire(fiber)
|
88
|
+
if !@connections.empty?
|
89
|
+
@busy_connections[fiber] = @connections.shift
|
90
|
+
elsif (@connections.length + @busy_connections.length) < @size
|
91
|
+
conn = @connection_proc.call
|
92
|
+
@busy_connections[fiber] = conn
|
93
|
+
else
|
94
|
+
Fiber.yield @queue << fiber
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Check if there are waiting fibers and
|
99
|
+
# try to process them
|
100
|
+
def process_queue
|
101
|
+
while !@connections.empty? and !@queue.empty?
|
102
|
+
fiber = @queue.shift
|
103
|
+
# What is really happening here?
|
104
|
+
# we are resuming a fiber from within
|
105
|
+
# another, should we call transfer insted?
|
106
|
+
fiber.resume @busy_connections[fiber] = @connections.shift
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end #FiberedConnectionPool
|
111
|
+
|
112
|
+
end #Pool
|
113
|
+
|
114
|
+
end #NeverBlock
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thin'
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
|
5
|
+
# Patch the thin server to use
|
6
|
+
# NeverBlock::Pool::FiberPool
|
7
|
+
# to be able to wrap requests
|
8
|
+
# in fibers
|
9
|
+
class Server
|
10
|
+
|
11
|
+
DEFAULT_FIBER_POOL_SIZE = 50
|
12
|
+
|
13
|
+
def fiber_pool
|
14
|
+
@fiber_pool ||= NB::Pool::FiberPool.new(DEFAULT_FIBER_POOL_SIZE)
|
15
|
+
end
|
16
|
+
|
17
|
+
end # Server
|
18
|
+
|
19
|
+
# A request is processed by wrapping it
|
20
|
+
# in a fiber from the fiber pool. If all
|
21
|
+
# the fibers are busy the request will
|
22
|
+
# wait in a queue to be picked up later.
|
23
|
+
# Meanwhile, the server will still be
|
24
|
+
# processing requests
|
25
|
+
class Connection < EventMachine::Connection
|
26
|
+
|
27
|
+
def process
|
28
|
+
@request.threaded = false
|
29
|
+
@backend.server.fiber_pool.spawn{post_process(pre_process)}
|
30
|
+
end
|
31
|
+
|
32
|
+
end # Connection
|
33
|
+
|
34
|
+
end # Thin
|
data/lib/neverblock.rb
ADDED
data/neverblock.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "neverblock"
|
3
|
+
s.version = "0.1.0"
|
4
|
+
s.date = "2008-08-13"
|
5
|
+
s.summary = "Utilities for non-blocking stack components"
|
6
|
+
s.email = "oldmoe@gmail.com"
|
7
|
+
s.homepage = "http://github.com/oldmoe/neverblock"
|
8
|
+
s.description = "NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner."
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Muhammad A. Ali", "Ahmed Sobhi"]
|
11
|
+
s.files = [
|
12
|
+
"neverblock.gemspec",
|
13
|
+
"README",
|
14
|
+
"lib/neverblock.rb",
|
15
|
+
"lib/never_block.rb",
|
16
|
+
"lib/never_block/extensions/fiber_extensions.rb",
|
17
|
+
"lib/never_block/pool/fiber_pool.rb",
|
18
|
+
"lib/never_block/pool/fibered_connection_pool.rb",
|
19
|
+
"lib/never_block/frameworks/rails.rb",
|
20
|
+
"lib/never_block/frameworks/activerecord.rb",
|
21
|
+
"lib/never_block/servers/thin.rb"]
|
22
|
+
s.rdoc_options = ["--main", "README"]
|
23
|
+
s.extra_rdoc_files = ["README"]
|
24
|
+
end
|
25
|
+
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oldmoe-neverblock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Muhammad A. Ali
|
8
|
+
- Ahmed Sobhi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-08-13 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: NeverBlock is a collection of classes and modules that help you write evented non-blocking applications in a seemingly blocking mannner.
|
18
|
+
email: oldmoe@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README
|
25
|
+
files:
|
26
|
+
- neverblock.gemspec
|
27
|
+
- README
|
28
|
+
- lib/neverblock.rb
|
29
|
+
- lib/never_block.rb
|
30
|
+
- lib/never_block/extensions/fiber_extensions.rb
|
31
|
+
- lib/never_block/pool/fiber_pool.rb
|
32
|
+
- lib/never_block/pool/fibered_connection_pool.rb
|
33
|
+
- lib/never_block/frameworks/rails.rb
|
34
|
+
- lib/never_block/frameworks/activerecord.rb
|
35
|
+
- lib/never_block/servers/thin.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/oldmoe/neverblock
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options:
|
40
|
+
- --main
|
41
|
+
- README
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: Utilities for non-blocking stack components
|
63
|
+
test_files: []
|
64
|
+
|