functional-ruby 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +154 -562
- data/lib/functional/agent.rb +130 -0
- data/lib/functional/all.rb +9 -1
- data/lib/functional/behavior.rb +72 -39
- data/lib/functional/cached_thread_pool.rb +122 -0
- data/lib/functional/concurrency.rb +32 -24
- data/lib/functional/core.rb +2 -62
- data/lib/functional/event.rb +53 -0
- data/lib/functional/event_machine_defer_proxy.rb +23 -0
- data/lib/functional/fixed_thread_pool.rb +89 -0
- data/lib/functional/future.rb +42 -0
- data/lib/functional/global_thread_pool.rb +3 -0
- data/lib/functional/obligation.rb +121 -0
- data/lib/functional/promise.rb +194 -0
- data/lib/functional/thread_pool.rb +61 -0
- data/lib/functional/utilities.rb +114 -0
- data/lib/functional/version.rb +1 -1
- data/lib/functional.rb +1 -0
- data/lib/functional_ruby.rb +1 -0
- data/md/behavior.md +147 -0
- data/md/concurrency.md +465 -0
- data/md/future.md +32 -0
- data/md/obligation.md +32 -0
- data/md/pattern_matching.md +512 -0
- data/md/promise.md +220 -0
- data/md/utilities.md +53 -0
- data/spec/functional/agent_spec.rb +405 -0
- data/spec/functional/behavior_spec.rb +12 -33
- data/spec/functional/cached_thread_pool_spec.rb +112 -0
- data/spec/functional/concurrency_spec.rb +55 -0
- data/spec/functional/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/functional/event_spec.rb +114 -0
- data/spec/functional/fixed_thread_pool_spec.rb +84 -0
- data/spec/functional/future_spec.rb +115 -0
- data/spec/functional/obligation_shared.rb +121 -0
- data/spec/functional/pattern_matching_spec.rb +10 -8
- data/spec/functional/promise_spec.rb +310 -0
- data/spec/functional/thread_pool_shared.rb +209 -0
- data/spec/functional/utilities_spec.rb +149 -0
- data/spec/spec_helper.rb +2 -0
- metadata +55 -5
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'functional/global_thread_pool'
|
5
|
+
|
6
|
+
module Functional
|
7
|
+
|
8
|
+
# An agent is a single atomic value that represents an identity. The current value
|
9
|
+
# of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
|
10
|
+
# the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
|
11
|
+
# will receive the current value of the agent as its sole parameter. The return value of the block
|
12
|
+
# will become the new value of the agent. Agents support two error handling modes: fail and continue.
|
13
|
+
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
14
|
+
class Agent
|
15
|
+
include Observable
|
16
|
+
|
17
|
+
TIMEOUT = 5
|
18
|
+
|
19
|
+
attr_reader :initial
|
20
|
+
attr_reader :timeout
|
21
|
+
|
22
|
+
def initialize(initial, timeout = TIMEOUT)
|
23
|
+
@value = initial
|
24
|
+
@timeout = timeout
|
25
|
+
@rescuers = []
|
26
|
+
@validator = nil
|
27
|
+
@queue = Queue.new
|
28
|
+
|
29
|
+
$GLOBAL_THREAD_POOL << proc{ work }
|
30
|
+
end
|
31
|
+
|
32
|
+
def value(timeout = 0) return @value; end
|
33
|
+
alias_method :deref, :value
|
34
|
+
|
35
|
+
def rescue(clazz = Exception, &block)
|
36
|
+
@rescuers << Rescuer.new(clazz, block) if block_given?
|
37
|
+
return self
|
38
|
+
end
|
39
|
+
alias_method :catch, :rescue
|
40
|
+
alias_method :on_error, :rescue
|
41
|
+
|
42
|
+
def validate(&block)
|
43
|
+
@validator = block if block_given?
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
alias_method :validates, :validate
|
47
|
+
alias_method :validate_with, :validate
|
48
|
+
alias_method :validates_with, :validate
|
49
|
+
|
50
|
+
def post(&block)
|
51
|
+
return @queue.length unless block_given?
|
52
|
+
@queue << block
|
53
|
+
return @queue.length
|
54
|
+
end
|
55
|
+
|
56
|
+
def <<(block)
|
57
|
+
self.post(&block)
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
def length
|
62
|
+
@queue.length
|
63
|
+
end
|
64
|
+
alias_method :size, :length
|
65
|
+
alias_method :count, :length
|
66
|
+
|
67
|
+
alias_method :add_watch, :add_observer
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @private
|
72
|
+
Rescuer = Struct.new(:clazz, :block)
|
73
|
+
|
74
|
+
# @private
|
75
|
+
def try_rescue(ex) # :nodoc:
|
76
|
+
rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
|
77
|
+
rescuer.block.call(ex) if rescuer
|
78
|
+
rescue Exception => e
|
79
|
+
# supress
|
80
|
+
end
|
81
|
+
|
82
|
+
# @private
|
83
|
+
def work # :nodoc:
|
84
|
+
loop do
|
85
|
+
Thread.pass
|
86
|
+
handler = @queue.pop
|
87
|
+
begin
|
88
|
+
result = Timeout.timeout(@timeout){
|
89
|
+
handler.call(@value)
|
90
|
+
}
|
91
|
+
if @validator.nil? || @validator.call(result)
|
92
|
+
@value = result
|
93
|
+
changed
|
94
|
+
notify_observers(Time.now, @value)
|
95
|
+
end
|
96
|
+
rescue Exception => ex
|
97
|
+
try_rescue(ex)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module Kernel
|
105
|
+
|
106
|
+
def agent(initial, timeout = Functional::Agent::TIMEOUT)
|
107
|
+
return Functional::Agent.new(initial, timeout)
|
108
|
+
end
|
109
|
+
module_function :agent
|
110
|
+
|
111
|
+
def deref(agent, timeout = nil)
|
112
|
+
if agent.respond_to?(:deref)
|
113
|
+
return agent.deref(timeout)
|
114
|
+
elsif agent.respond_to?(:value)
|
115
|
+
return agent.deref(timeout)
|
116
|
+
else
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
module_function :deref
|
121
|
+
|
122
|
+
def post(agent, &block)
|
123
|
+
if agent.respond_to?(:post)
|
124
|
+
return agent.post(&block)
|
125
|
+
else
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
module_function :deref
|
130
|
+
end
|
data/lib/functional/all.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
+
require 'functional/agent'
|
1
2
|
require 'functional/behavior'
|
3
|
+
require 'functional/cached_thread_pool'
|
2
4
|
require 'functional/concurrency'
|
3
|
-
require 'functional/
|
5
|
+
require 'functional/event'
|
6
|
+
require 'functional/fixed_thread_pool'
|
7
|
+
require 'functional/future'
|
8
|
+
require 'functional/obligation'
|
4
9
|
require 'functional/pattern_matching'
|
10
|
+
require 'functional/promise'
|
11
|
+
require 'functional/thread_pool'
|
12
|
+
require 'functional/utilities'
|
5
13
|
require 'functional/version'
|
data/lib/functional/behavior.rb
CHANGED
@@ -1,12 +1,78 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Kernel
|
2
|
+
|
3
|
+
BehaviorError = Class.new(StandardError)
|
4
|
+
|
5
|
+
# Define a behavioral specification (interface).
|
6
|
+
#
|
7
|
+
# @param name [Symbol] the name of the behavior
|
8
|
+
# @param functions [Hash] function names and their arity as key/value pairs
|
9
|
+
def behavior_info(name, functions = {})
|
10
|
+
$__behavior_info__ ||= {}
|
11
|
+
$__behavior_info__[name.to_sym] = functions.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
12
|
+
end
|
13
|
+
|
14
|
+
alias :behaviour_info :behavior_info
|
15
|
+
alias :interface :behavior_info
|
16
|
+
|
17
|
+
module_function :behavior_info
|
18
|
+
module_function :behaviour_info
|
19
|
+
module_function :interface
|
20
|
+
|
21
|
+
# Specify a #behavior_info to enforce on the enclosing class
|
22
|
+
#
|
23
|
+
# @param name [Symbol] name of the #behavior_info being implemented
|
24
|
+
def behavior(name)
|
25
|
+
|
26
|
+
name = name.to_sym
|
27
|
+
raise BehaviorError.new("undefined behavior '#{name}'") if $__behavior_info__[name].nil?
|
28
|
+
|
29
|
+
clazz = self.method(:behavior).receiver
|
30
|
+
|
31
|
+
unless clazz.instance_methods(false).include?(:behaviors)
|
32
|
+
class << clazz
|
33
|
+
def behaviors
|
34
|
+
@behaviors ||= []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
clazz.behaviors << name
|
40
|
+
|
41
|
+
class << clazz
|
42
|
+
def new(*args, &block)
|
43
|
+
name = self.behaviors.first
|
44
|
+
obj = super
|
45
|
+
unless obj.behaves_as?(name)
|
46
|
+
raise BehaviorError.new("undefined callback functions in #{self} (behavior '#{name}')")
|
47
|
+
else
|
48
|
+
return obj
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
5
53
|
|
6
|
-
alias :
|
7
|
-
alias :
|
54
|
+
alias :behaviour :behavior
|
55
|
+
alias :behaves_as :behavior
|
56
|
+
|
57
|
+
module_function :behavior
|
58
|
+
module_function :behaviour
|
59
|
+
module_function :behaves_as
|
60
|
+
end
|
8
61
|
|
9
62
|
class Object
|
63
|
+
|
64
|
+
# Does the object implement the given #behavior_info?
|
65
|
+
#
|
66
|
+
# @note Will return true if the object implements the
|
67
|
+
# required methods. The object's class hierarchy does
|
68
|
+
# not necessarily have to include a corresponding
|
69
|
+
# #behavior call.
|
70
|
+
#
|
71
|
+
# @param name [Symbol] name of the #behavior_info to
|
72
|
+
# verify behavior against.
|
73
|
+
#
|
74
|
+
# @return [Boolean] whether or not the required public
|
75
|
+
# methods are implemented
|
10
76
|
def behaves_as?(name)
|
11
77
|
|
12
78
|
name = name.to_sym
|
@@ -24,36 +90,3 @@ class Object
|
|
24
90
|
return true
|
25
91
|
end
|
26
92
|
end
|
27
|
-
|
28
|
-
def behavior(name)
|
29
|
-
|
30
|
-
name = name.to_sym
|
31
|
-
raise ArgumentError.new("undefined behavior '#{name}'") if $__behavior_info__[name].nil?
|
32
|
-
|
33
|
-
clazz = self.method(:behavior).receiver
|
34
|
-
|
35
|
-
unless clazz.instance_methods(false).include?(:behaviors)
|
36
|
-
class << clazz
|
37
|
-
def behaviors
|
38
|
-
@behaviors ||= []
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
clazz.behaviors << name
|
44
|
-
|
45
|
-
class << clazz
|
46
|
-
def new(*args, &block)
|
47
|
-
name = self.behaviors.first
|
48
|
-
obj = super
|
49
|
-
unless obj.behaves_as?(name)
|
50
|
-
raise ArgumentError.new("undefined callback functions in #{self} (behavior '#{name}')")
|
51
|
-
else
|
52
|
-
return obj
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
alias :behaviour :behavior
|
59
|
-
alias :behaves_as :behavior
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'functional/thread_pool'
|
4
|
+
require 'functional/utilities'
|
5
|
+
|
6
|
+
module Functional
|
7
|
+
|
8
|
+
def self.new_cached_thread_pool
|
9
|
+
return CachedThreadPool.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class CachedThreadPool < ThreadPool
|
13
|
+
behavior(:thread_pool)
|
14
|
+
|
15
|
+
DEFAULT_GC_INTERVAL = 60
|
16
|
+
DEFAULT_THREAD_IDLETIME = 60
|
17
|
+
|
18
|
+
attr_reader :working
|
19
|
+
|
20
|
+
def initialize(opts = {})
|
21
|
+
@gc_interval = opts[:gc_interval] || DEFAULT_GC_INTERVAL
|
22
|
+
@thread_idletime = opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME
|
23
|
+
super()
|
24
|
+
@working = 0
|
25
|
+
@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def kill
|
29
|
+
@status = :killed
|
30
|
+
@mutex.synchronize do
|
31
|
+
@pool.each{|t| Thread.kill(t.thread) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
return @pool.length
|
37
|
+
end
|
38
|
+
|
39
|
+
def post(*args, &block)
|
40
|
+
raise ArgumentError.new('no block given') unless block_given?
|
41
|
+
if running?
|
42
|
+
collect_garbage if @pool.empty?
|
43
|
+
@mutex.synchronize do
|
44
|
+
if @working >= @pool.length
|
45
|
+
create_worker_thread
|
46
|
+
end
|
47
|
+
@queue << [args, block]
|
48
|
+
end
|
49
|
+
return true
|
50
|
+
else
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @private
|
56
|
+
def status # :nodoc:
|
57
|
+
@mutex.synchronize do
|
58
|
+
@pool.collect do |worker|
|
59
|
+
[
|
60
|
+
worker.status,
|
61
|
+
worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
|
62
|
+
worker.thread.status
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
Worker = Struct.new(:status, :idletime, :thread)
|
71
|
+
|
72
|
+
# @private
|
73
|
+
def create_worker_thread # :nodoc:
|
74
|
+
worker = Worker.new(:idle, timestamp, nil)
|
75
|
+
|
76
|
+
worker.thread = Thread.new(worker) do |me|
|
77
|
+
|
78
|
+
loop do
|
79
|
+
task = @queue.pop
|
80
|
+
|
81
|
+
@working += 1
|
82
|
+
me.status = :working
|
83
|
+
|
84
|
+
if task == :stop
|
85
|
+
me.status = :stopping
|
86
|
+
break
|
87
|
+
else
|
88
|
+
task.last.call(*task.first)
|
89
|
+
@working -= 1
|
90
|
+
me.status = :idle
|
91
|
+
me.idletime = timestamp
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@pool.delete(me)
|
96
|
+
if @pool.empty?
|
97
|
+
@termination.set
|
98
|
+
@status = :shutdown unless killed?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@pool << worker
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
def collect_garbage # :nodoc:
|
107
|
+
@collector = Thread.new do
|
108
|
+
loop do
|
109
|
+
sleep(@gc_interval)
|
110
|
+
@mutex.synchronize do
|
111
|
+
@pool.reject! do |worker|
|
112
|
+
worker.thread.status.nil? ||
|
113
|
+
(worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
@working = @pool.count{|worker| worker.status == :working}
|
117
|
+
break if @pool.empty?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -1,27 +1,35 @@
|
|
1
|
-
|
2
|
-
# http://www.lesismore.co.za/rubyenums.html
|
3
|
-
# http://gistflow.com/posts/682-ruby-enums-approaches
|
1
|
+
require 'thread'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# * apply
|
9
|
-
# * assert
|
10
|
-
# * await
|
11
|
-
# * future
|
12
|
-
# * memoize
|
13
|
-
# * promise
|
14
|
-
# * send
|
15
|
-
# * slurp
|
3
|
+
require 'functional/agent'
|
4
|
+
require 'functional/future'
|
5
|
+
require 'functional/promise'
|
16
6
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# * promise = ala JavaScript http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
|
21
|
-
# * ada range type - http://en.wikibooks.org/wiki/Ada_Programming/Types/range
|
22
|
-
# * slurpee - slurp + erb parsing
|
23
|
-
# * spawn/send/receive - http://www.erlang.org/doc/reference_manual/processes.html
|
7
|
+
require 'functional/thread_pool'
|
8
|
+
require 'functional/cached_thread_pool'
|
9
|
+
require 'functional/fixed_thread_pool'
|
24
10
|
|
25
|
-
|
26
|
-
|
27
|
-
|
11
|
+
require 'functional/global_thread_pool'
|
12
|
+
|
13
|
+
require 'functional/event_machine_defer_proxy' if defined?(EventMachine)
|
14
|
+
|
15
|
+
module Kernel
|
16
|
+
|
17
|
+
# Post the given agruments and block to the Global Thread Pool.
|
18
|
+
#
|
19
|
+
# @param args [Array] zero or more arguments for the block
|
20
|
+
# @param block [Proc] operation to be performed concurrently
|
21
|
+
#
|
22
|
+
# @return [true,false] success/failre of thread creation
|
23
|
+
#
|
24
|
+
# @note Althought based on Go's goroutines and Erlang's spawn/1,
|
25
|
+
# Ruby has a vastly different runtime. Threads aren't nearly as
|
26
|
+
# efficient in Ruby. Use this function appropriately.
|
27
|
+
#
|
28
|
+
# @see http://golang.org/doc/effective_go.html#goroutines
|
29
|
+
# @see https://gobyexample.com/goroutines
|
30
|
+
def go(*args, &block)
|
31
|
+
return false unless block_given?
|
32
|
+
$GLOBAL_THREAD_POOL.post(*args, &block)
|
33
|
+
end
|
34
|
+
module_function :go
|
35
|
+
end
|
data/lib/functional/core.rb
CHANGED
@@ -1,62 +1,2 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
Infinity = 1/0.0 unless defined?(Infinity)
|
5
|
-
NaN = 0/0.0 unless defined?(NaN)
|
6
|
-
|
7
|
-
module Kernel
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def repl?
|
12
|
-
return ($0 == 'irb' || $0 == 'pry' || $0 == 'script/rails' || !!($0 =~ /bin\/bundle$/))
|
13
|
-
end
|
14
|
-
module_function :repl?
|
15
|
-
|
16
|
-
def safe(*args, &block)
|
17
|
-
raise ArgumentError.new('no block given') unless block_given?
|
18
|
-
result = nil
|
19
|
-
t = Thread.new do
|
20
|
-
$SAFE = 3
|
21
|
-
result = self.instance_exec(*args, &block)
|
22
|
-
end
|
23
|
-
t.join
|
24
|
-
return result
|
25
|
-
end
|
26
|
-
module_function :safe
|
27
|
-
|
28
|
-
# http://rhaseventh.blogspot.com/2008/07/ruby-and-rails-how-to-get-pp-pretty.html
|
29
|
-
def pp_s(*objs)
|
30
|
-
s = StringIO.new
|
31
|
-
objs.each {|obj|
|
32
|
-
PP.pp(obj, s)
|
33
|
-
}
|
34
|
-
s.rewind
|
35
|
-
s.read
|
36
|
-
end
|
37
|
-
module_function :pp_s
|
38
|
-
|
39
|
-
# Compute the difference (delta) between two values.
|
40
|
-
#
|
41
|
-
# When a block is given the block will be applied to both arguments.
|
42
|
-
# Using a block in this way allows computation against a specific field
|
43
|
-
# in a data set of hashes or objects.
|
44
|
-
#
|
45
|
-
# @yield iterates over each element in the data set
|
46
|
-
# @yieldparam item each element in the data set
|
47
|
-
#
|
48
|
-
# @param [Object] v1 the first value
|
49
|
-
# @param [Object] v2 the second value
|
50
|
-
#
|
51
|
-
# @return [Float] positive value representing the difference
|
52
|
-
# between the two parameters
|
53
|
-
def delta(v1, v2)
|
54
|
-
if block_given?
|
55
|
-
v1 = yield(v1)
|
56
|
-
v2 = yield(v2)
|
57
|
-
end
|
58
|
-
return (v1 - v2).abs
|
59
|
-
end
|
60
|
-
module_function :delta
|
61
|
-
|
62
|
-
end
|
1
|
+
require 'functional/behavior'
|
2
|
+
require 'functional/pattern_matching'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Functional
|
5
|
+
|
6
|
+
class Event
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@set = false
|
10
|
+
@notifier = Queue.new
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@waiting = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def set?
|
16
|
+
return @set == true
|
17
|
+
end
|
18
|
+
|
19
|
+
def set
|
20
|
+
return true if set?
|
21
|
+
@mutex.synchronize {
|
22
|
+
@set = true
|
23
|
+
while @waiting > 0
|
24
|
+
@notifier << :set
|
25
|
+
@waiting -= 1
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset
|
31
|
+
@mutex.synchronize {
|
32
|
+
@set = false
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def wait(timeout = nil)
|
37
|
+
return true if set?
|
38
|
+
|
39
|
+
if timeout.nil?
|
40
|
+
@waiting += 1
|
41
|
+
@notifier.pop
|
42
|
+
else
|
43
|
+
Timeout::timeout(timeout) do
|
44
|
+
@waiting += 1
|
45
|
+
@notifier.pop
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return true
|
49
|
+
rescue Timeout::Error
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'functional/global_thread_pool'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
class EventMachineDeferProxy
|
6
|
+
behavior(:global_thread_pool)
|
7
|
+
|
8
|
+
def post(*args, &block)
|
9
|
+
if args.empty?
|
10
|
+
EventMachine.defer(block)
|
11
|
+
else
|
12
|
+
new_block = proc{ block.call(*args) }
|
13
|
+
EventMachine.defer(new_block)
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(block)
|
19
|
+
EventMachine.defer(block)
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'functional/thread_pool'
|
4
|
+
require 'functional/event'
|
5
|
+
|
6
|
+
module Functional
|
7
|
+
|
8
|
+
def self.new_fixed_thread_pool(size)
|
9
|
+
return FixedThreadPool.new(size)
|
10
|
+
end
|
11
|
+
|
12
|
+
class FixedThreadPool < ThreadPool
|
13
|
+
behavior(:thread_pool)
|
14
|
+
|
15
|
+
MIN_POOL_SIZE = 1
|
16
|
+
MAX_POOL_SIZE = 1024
|
17
|
+
|
18
|
+
def initialize(size)
|
19
|
+
super()
|
20
|
+
if size < MIN_POOL_SIZE || size > MAX_POOL_SIZE
|
21
|
+
raise ArgumentError.new("size must be between #{MIN_POOL_SIZE} and #{MAX_POOL_SIZE}")
|
22
|
+
end
|
23
|
+
|
24
|
+
@pool = size.times.collect{ create_worker_thread }
|
25
|
+
collect_garbage
|
26
|
+
end
|
27
|
+
|
28
|
+
def kill
|
29
|
+
@status = :killed
|
30
|
+
@pool.each{|t| Thread.kill(t) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
if running?
|
35
|
+
return @pool.length
|
36
|
+
else
|
37
|
+
return 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post(*args, &block)
|
42
|
+
raise ArgumentError.new('no block given') unless block_given?
|
43
|
+
if running?
|
44
|
+
@queue << [args, block]
|
45
|
+
return true
|
46
|
+
else
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @private
|
52
|
+
def status # :nodoc:
|
53
|
+
@pool.collect{|t| t.status }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def create_worker_thread # :nodoc:
|
60
|
+
Thread.new do
|
61
|
+
loop do
|
62
|
+
task = @queue.pop
|
63
|
+
if task == :stop
|
64
|
+
break
|
65
|
+
else
|
66
|
+
task.last.call(*task.first)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@pool.delete(Thread.current)
|
70
|
+
if @pool.empty?
|
71
|
+
@termination.set
|
72
|
+
@status = :shutdown unless killed?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @private
|
78
|
+
def collect_garbage # :nodoc:
|
79
|
+
@collector = Thread.new do
|
80
|
+
sleep(1)
|
81
|
+
@pool.size.times do |i|
|
82
|
+
if @pool[i].status.nil?
|
83
|
+
@pool[i] = create_worker_thread
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|