functional-ruby 0.5.0 → 0.6.0
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.
- 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
|