ActionPool 0.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/ActionPool-0.0.1.gem +0 -0
- data/README +40 -0
- data/actionpool.gemspec +12 -0
- data/lib/actionpool/Exceptions.rb +19 -0
- data/lib/actionpool/LogHelper.rb +26 -0
- data/lib/actionpool/Pool.rb +158 -0
- data/lib/actionpool/Thread.rb +71 -0
- data/lib/actionpool.rb +1 -0
- metadata +62 -0
File without changes
|
data/README
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
== ActionPool ==
|
2
|
+
~Who doesn't love a pool with action?~
|
3
|
+
|
4
|
+
ActionPool is just a simple thread pool. It allows
|
5
|
+
for various contraints and resizing in a pretty
|
6
|
+
easy and unobtrusive manner. You can set limits
|
7
|
+
on how long tasks are worked on, as well as on the
|
8
|
+
life of a thread. For things that like to use lots
|
9
|
+
threads, it can be helpful to reuse threads instead of
|
10
|
+
constantly recreating them.
|
11
|
+
|
12
|
+
install (easy):
|
13
|
+
|
14
|
+
gem install ActionPool
|
15
|
+
|
16
|
+
install (less easy):
|
17
|
+
|
18
|
+
git clone http://github.com/spox/actionpool.git
|
19
|
+
cd actionpool
|
20
|
+
gem build actionpool.gemspec
|
21
|
+
gem install ActionPool-x.x.x.gem
|
22
|
+
|
23
|
+
It has RDocs. They are where ever your rubygems installed them.
|
24
|
+
They will probably be helpful to look over.
|
25
|
+
|
26
|
+
Simple example of using the pool:
|
27
|
+
|
28
|
+
pool = ActionPool::Pool.new
|
29
|
+
pool.process do
|
30
|
+
puts "Hello world"
|
31
|
+
sleep(1)
|
32
|
+
puts "Goodbye world"
|
33
|
+
end
|
34
|
+
puts "Hello to you too"
|
35
|
+
|
36
|
+
Result:
|
37
|
+
Hello world
|
38
|
+
Hello to you too
|
39
|
+
Goodbye world
|
40
|
+
|
data/actionpool.gemspec
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
spec = Gem::Specification.new do |s|
|
2
|
+
s.name = 'ActionPool'
|
3
|
+
s.author = %q(spox)
|
4
|
+
s.email = %q(spox@modspox.com)
|
5
|
+
s.version = '0.0.1'
|
6
|
+
s.summary = %q(Thread Pool)
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.files = Dir['**/*']
|
10
|
+
s.require_paths = %w(lib)
|
11
|
+
s.required_ruby_version = '>= 1.8.6'
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActionPool
|
2
|
+
|
3
|
+
class InvalidType < Exception
|
4
|
+
attr_reader :given
|
5
|
+
attr_reader :expected
|
6
|
+
def initialize(g,e)
|
7
|
+
@given = given
|
8
|
+
@expected = e
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"Given type: #{g} Expected type: #{e}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class InvalidValue < Exception
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module ActionPool
|
4
|
+
class LogHelper
|
5
|
+
|
6
|
+
def initialize(logger=nil)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def info(m)
|
11
|
+
@logger.info(m) unless @logger.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def warn(m)
|
15
|
+
@logger.warn(m) unless @logger.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def fatal(m)
|
19
|
+
@logger.fatal(m) unless @logger.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def error(m)
|
23
|
+
@logger.error(m) unless @logger.nil?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'actionpool/Exceptions'
|
2
|
+
require 'actionpool/Thread'
|
3
|
+
require 'actionpool/LogHelper'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
module ActionPool
|
7
|
+
class Pool
|
8
|
+
|
9
|
+
# min_threads:: minimum number of threads in pool
|
10
|
+
# max_threads:: maximum number of threads in pool
|
11
|
+
# t_to:: thread timeout waiting for action to process
|
12
|
+
# a_to:: maximum time action may be worked on before aborting
|
13
|
+
# logger:: logger to print logging messages to
|
14
|
+
# Creates a new pool
|
15
|
+
def initialize(min_threads=10, max_threads=100, t_to=60, a_to=nil, logger=nil)
|
16
|
+
@logger = LogHelper.new(logger)
|
17
|
+
@queue = Queue.new
|
18
|
+
@threads = []
|
19
|
+
@lock = Mutex.new
|
20
|
+
@thread_timeout = t_to
|
21
|
+
@action_timeout = a_to
|
22
|
+
@min_threads = min_threads
|
23
|
+
@max_threads = max_threads
|
24
|
+
@min_threads.times{create_thread}
|
25
|
+
end
|
26
|
+
|
27
|
+
# force:: force creation of a new thread
|
28
|
+
# Create a new thread for pool
|
29
|
+
def create_thread(force=false)
|
30
|
+
return nil unless @threads.size < @max_threads || force
|
31
|
+
@logger.info('Pool is creating a new thread')
|
32
|
+
pt = ActionPool::Thread.new(self, @thread_timeout, @action_timeout, @logger)
|
33
|
+
@threads << pt
|
34
|
+
return pt
|
35
|
+
end
|
36
|
+
|
37
|
+
# force:: force immediate stop
|
38
|
+
# Stop the pool
|
39
|
+
def shutdown(force=false)
|
40
|
+
@logger.info("Pool is now shutting down #{force ? 'using force' : ''}")
|
41
|
+
@threads.each{|t|t.stop}
|
42
|
+
until(size < 1) do
|
43
|
+
@queue << lambda{}
|
44
|
+
sleep(0.1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# action:: proc to be executed
|
49
|
+
# Add a new proc/lambda to be executed (alias for queue)
|
50
|
+
def <<(action)
|
51
|
+
queue(action)
|
52
|
+
end
|
53
|
+
|
54
|
+
# action:: proc to be executed
|
55
|
+
# Add a new proc/lambda to be executed
|
56
|
+
def queue(action)
|
57
|
+
raise InvalidType.new(action.class, Proc) unless action.is_a?(Proc)
|
58
|
+
@queue << action
|
59
|
+
start_thread if size > min
|
60
|
+
end
|
61
|
+
|
62
|
+
# block:: block to process
|
63
|
+
# Adds a block to be processed
|
64
|
+
def process(&block)
|
65
|
+
queue(block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Current size of pool
|
69
|
+
def size
|
70
|
+
@threads.size
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maximum allowed number of threads
|
74
|
+
def max
|
75
|
+
@max_threads
|
76
|
+
end
|
77
|
+
|
78
|
+
# Minimum allowed number of threads
|
79
|
+
def min
|
80
|
+
@min_threads
|
81
|
+
end
|
82
|
+
|
83
|
+
# m:: new max
|
84
|
+
# Set maximum number of threads
|
85
|
+
def max=(m)
|
86
|
+
m = m.to_i
|
87
|
+
raise InvalidValue.new unless m > 0
|
88
|
+
@max_threads = m
|
89
|
+
end
|
90
|
+
|
91
|
+
# m:: new min
|
92
|
+
# Set minimum number of threads
|
93
|
+
def min=(m)
|
94
|
+
m = m.to_i
|
95
|
+
raise InvalidValue.new unless m > 0 && m <= @max_threads
|
96
|
+
@min_threads = m
|
97
|
+
resize if m < size
|
98
|
+
end
|
99
|
+
|
100
|
+
# t:: ActionPool::Thread to remove
|
101
|
+
# Removes a thread from the pool
|
102
|
+
def remove(t)
|
103
|
+
@threads.delete(t)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Maximum number of seconds a thread
|
107
|
+
# is allowed to idle in the pool.
|
108
|
+
# (nil means thread life is infinite)
|
109
|
+
def thread_timeout
|
110
|
+
@thread_timeout
|
111
|
+
end
|
112
|
+
|
113
|
+
# Maximum number of seconds a thread
|
114
|
+
# is allowed to work on a given action
|
115
|
+
# (nil means thread is given unlimited
|
116
|
+
# time to work on action)
|
117
|
+
def action_timeout
|
118
|
+
@action_timeout
|
119
|
+
end
|
120
|
+
|
121
|
+
# t:: timeout in seconds (nil for infinite)
|
122
|
+
# Set maximum allowed time thead may idle in pool
|
123
|
+
def thread_timeout=(t)
|
124
|
+
t = to_i unless t.nil?
|
125
|
+
raise InvalidValue.new unless t > 0 || t.nil?
|
126
|
+
@thread_timeout = t
|
127
|
+
end
|
128
|
+
|
129
|
+
# t:: timeout in seconds (nil for infinte)
|
130
|
+
# Set maximum allowed time thread may work
|
131
|
+
# on a given action
|
132
|
+
def action_timeout=(t)
|
133
|
+
t = to_i unless t.nil?
|
134
|
+
raise InvalidValue.new unless t > 0 || t.nil?
|
135
|
+
@action_timeout = t
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the next action to be processed
|
139
|
+
def action
|
140
|
+
@queue.pop
|
141
|
+
end
|
142
|
+
|
143
|
+
# Number of actions in the queue
|
144
|
+
def action_size
|
145
|
+
@queue.size
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def resize
|
151
|
+
@logger.info("Pool is being resized to stated minimum: #{min}")
|
152
|
+
size - min.times do
|
153
|
+
t = @threads.shift
|
154
|
+
t.stop
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module ActionPool
|
4
|
+
class Thread
|
5
|
+
# pool:: pool thread is associated with
|
6
|
+
# t_timeout:: max time a thread is allowed to wait for action
|
7
|
+
# a_timeout:: max time thread is allowed to work
|
8
|
+
# logger:: LogHelper for logging messages
|
9
|
+
# Create a new thread
|
10
|
+
def initialize(pool, t_timeout, a_timeout, logger=nil)
|
11
|
+
@pool = pool
|
12
|
+
@pool_timeout = t_timeout.nil? ? 0 : t_timeout
|
13
|
+
@action_timeout = a_timeout.nil? ? 0 : a_timeout
|
14
|
+
@kill = false
|
15
|
+
@logger = logger
|
16
|
+
@thread = ::Thread.new{ start_thread }
|
17
|
+
end
|
18
|
+
|
19
|
+
# stop the thread
|
20
|
+
def stop(force=false)
|
21
|
+
@kill = true
|
22
|
+
@thread.kill if force
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def start_thread
|
28
|
+
begin
|
29
|
+
@logger.info("New pool thread is starting (#{self})")
|
30
|
+
until(@kill) do
|
31
|
+
begin
|
32
|
+
action = nil
|
33
|
+
if(@pool.size > @pool.min)
|
34
|
+
Timeout::timeout(@thread_timeout) do
|
35
|
+
action = @pool.action
|
36
|
+
end
|
37
|
+
else
|
38
|
+
action = @pool.action
|
39
|
+
end
|
40
|
+
run(action) unless action.nil?
|
41
|
+
rescue Timeout::Error => boom
|
42
|
+
@kill = true
|
43
|
+
rescue Object => boom
|
44
|
+
@logger.error("Pool thread caught an exception: #{boom}\n#{boom.backtrace.join("\n")}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue Object => boom
|
48
|
+
@logger.error("Pool thread caught an exception: #{boom}\n#{boom.backtrace.join("\n")}")
|
49
|
+
ensure
|
50
|
+
@logger.info("Pool thread is shutting down (#{self})")
|
51
|
+
@pool.remove(self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def run(action)
|
56
|
+
begin
|
57
|
+
if(@action_timeout > 0)
|
58
|
+
Timeout::timeout(@action_timeout) do
|
59
|
+
action.call
|
60
|
+
end
|
61
|
+
else
|
62
|
+
action.call
|
63
|
+
end
|
64
|
+
rescue Timeout::Error => boom
|
65
|
+
@logger.warn("Pool thread reached max execution time for action: #{boom}")
|
66
|
+
rescue Object => boom
|
67
|
+
@logger.error("Pool thread caught an exception running action: #{boom}\n#{boom.backtrace.join("\n")}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/actionpool.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'actionpool/Pool'
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ActionPool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- spox
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-13 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: spox@modspox.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib
|
26
|
+
- lib/actionpool
|
27
|
+
- lib/actionpool/Exceptions.rb
|
28
|
+
- lib/actionpool/LogHelper.rb
|
29
|
+
- lib/actionpool/Pool.rb
|
30
|
+
- lib/actionpool/Thread.rb
|
31
|
+
- lib/actionpool.rb
|
32
|
+
- README
|
33
|
+
- ActionPool-0.0.1.gem
|
34
|
+
- actionpool.gemspec
|
35
|
+
has_rdoc: true
|
36
|
+
homepage:
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.8.6
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.0
|
58
|
+
signing_key:
|
59
|
+
specification_version: 2
|
60
|
+
summary: Thread Pool
|
61
|
+
test_files: []
|
62
|
+
|