fbp 0.1.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 +7 -0
- data/lib/fbp.rb +464 -0
- data/lib/fbp/aggregator-node.rb +31 -0
- data/lib/fbp/assign-node.rb +41 -0
- data/lib/fbp/concatenate-node.rb +66 -0
- data/lib/fbp/constants.rb +13 -0
- data/lib/fbp/counter-node.rb +50 -0
- data/lib/fbp/decode-node.rb +34 -0
- data/lib/fbp/encode-node.rb +31 -0
- data/lib/fbp/flow-node.rb +369 -0
- data/lib/fbp/fpb-thread-pool.rb +91 -0
- data/lib/fbp/selector-node.rb +131 -0
- data/lib/fbp/sort-node.rb +68 -0
- data/lib/fbp/text_file_reader_node.rb +80 -0
- data/lib/fbp/text_file_writer_node.rb +65 -0
- data/lib/fbp/version.rb +3 -0
- data/lib/thread-pool/thread-pool.rb +150 -0
- metadata +91 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require "thread-pool/thread-pool.rb"
|
2
|
+
|
3
|
+
module Fbp
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
=== Description
|
7
|
+
The Node pool class is built upon Kim Burgestrand Pool class which provides thread pool management
|
8
|
+
for Ruby. The Node pool provides a standard set of module functions that allow for setting the
|
9
|
+
number of threads in the Pool and to shutdown the Pool.
|
10
|
+
=end
|
11
|
+
class Node_pool #:nodoc:
|
12
|
+
|
13
|
+
attr_reader :pool #:nodoc:
|
14
|
+
attr_reader :num_threads #:nodoc:
|
15
|
+
|
16
|
+
def initialize (num_threads = 10) #:nodoc:
|
17
|
+
@num_threads = num_threads
|
18
|
+
@pool = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def make_pool #:nodoc:
|
22
|
+
return false if !@pool.nil?
|
23
|
+
@pool = Pool.new(@num_threads)
|
24
|
+
end
|
25
|
+
|
26
|
+
def num_threads=(num_threads) #:nodoc:
|
27
|
+
@num_threads = num_threads if @pool.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def shutdown #:nodoc:
|
31
|
+
@pool.shutdown if !@pool.nil?
|
32
|
+
@pool = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
@@node_pool = Node_pool.new #:nodoc:
|
38
|
+
|
39
|
+
=begin rdoc
|
40
|
+
The num_threads= sets the number of threads that will be used by the thread pool
|
41
|
+
that will be used for any Fbp network. The default value is 10 threads. This
|
42
|
+
function <b>only</b> works <b>before></b> calling Fbp::make_pool. After that this
|
43
|
+
function will do nothing
|
44
|
+
=== Parameters
|
45
|
+
+num_threads+ - The number of threads that should be used in the Thread Pool
|
46
|
+
=end
|
47
|
+
public
|
48
|
+
def self.num_threads= (num_threads)
|
49
|
+
@@node_pool.num_threads = num_threads
|
50
|
+
end
|
51
|
+
|
52
|
+
=begin
|
53
|
+
Returns that number of threads that are in the Pool
|
54
|
+
=end
|
55
|
+
public
|
56
|
+
def self.num_threads
|
57
|
+
@@node_pool.num_threads
|
58
|
+
end
|
59
|
+
|
60
|
+
=begin
|
61
|
+
This function will create the thread pool that will be used by any Fbp network.
|
62
|
+
This function <b>must</b> be called before executing any Fbp network.
|
63
|
+
=end
|
64
|
+
public
|
65
|
+
def self.make_pool
|
66
|
+
@@node_pool.make_pool
|
67
|
+
end
|
68
|
+
|
69
|
+
=begin
|
70
|
+
This function will return back if Fbp::make_pool has been called and a Pool has
|
71
|
+
been created.
|
72
|
+
=end
|
73
|
+
public
|
74
|
+
def self.has_pool
|
75
|
+
!@@node_pool.pool.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
=begin
|
79
|
+
This function will stop all running threads in the Fbp pool. Calling this function
|
80
|
+
will kill all Fbp processing.
|
81
|
+
=end
|
82
|
+
public
|
83
|
+
def self.shutdown
|
84
|
+
!@@node_pool.shutdown
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
def self.schedule(&block) #:nodoc:
|
89
|
+
@@node_pool.pool.schedule { block.call } if !@@node_pool.pool.nil?
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "fbp/constants"
|
2
|
+
|
3
|
+
module Fbp
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
<b>Selector_node</b>
|
7
|
+
=== Description
|
8
|
+
The Selector node class provides for comparing the value(s) in an IP and with set value(s) and the
|
9
|
+
out come of the comparison will determine which output channel the IP will be sent to and thus splitting the
|
10
|
+
incoming stream to different downstream nodes.
|
11
|
+
===
|
12
|
+
The Selector_node class requires an IIP of the form:
|
13
|
+
|
14
|
+
Selector
|
15
|
+
{:key => {:comparison => comparison, :value => value, :match => :output_name, :reject => :output_name}}
|
16
|
+
|
17
|
+
:key
|
18
|
+
This is the symbol that will be used to match against keys in an IP
|
19
|
+
|
20
|
+
:comparison
|
21
|
+
This is a constant define in constants.rb for comparison operations
|
22
|
+
|
23
|
+
:value
|
24
|
+
This is the value that will be used to check against the corresponding value in an IP
|
25
|
+
|
26
|
+
:match
|
27
|
+
The name of the output queue that will be written to for IPs that match the selector
|
28
|
+
|
29
|
+
:reject
|
30
|
+
The name of the output queue that will be written to for IPs that do not match the selector
|
31
|
+
|
32
|
+
There may be multiple selectors with different key values that will match different keys in an IP
|
33
|
+
|
34
|
+
When an IP arrives at a Selector_node, the do_node_work will match keys in the IP with keys in the
|
35
|
+
selectors hash. If there is a match, then the comparison stipulated in the selector will be done on
|
36
|
+
the value in the IP against the value in the selector. If the comparison is a match then the IP
|
37
|
+
will be written to the output named in the :match item otherwise it will be written to the output
|
38
|
+
named in the :reject item.
|
39
|
+
|
40
|
+
If an IP does not have any keys that match any selectors the IP will be written to the output named :output
|
41
|
+
=end
|
42
|
+
class Selector_node < Node
|
43
|
+
=begin rdoc
|
44
|
+
When creating a new Selector_node instance one can provide an
|
45
|
+
array of selector records.
|
46
|
+
=end
|
47
|
+
def initialize(selector = nil)
|
48
|
+
super()
|
49
|
+
@options[:selectors] = selector if !selector.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
=begin rdoc
|
53
|
+
Checks to see if this Selector_node instance has an array of Selector records.
|
54
|
+
If it does have an array of Selector records then true will be returned
|
55
|
+
otherwise false. A Selector_node instance will not execute until it has
|
56
|
+
an array of Selector records
|
57
|
+
=end
|
58
|
+
def is_ready_to_run?
|
59
|
+
@options.has_key? :selectors
|
60
|
+
end
|
61
|
+
|
62
|
+
=begin rdoc
|
63
|
+
Each IP that is presented for processing will be compared to the array of
|
64
|
+
selector records associated with this instance. The selector records
|
65
|
+
determine how the IP will be output. If there is a match, then the IP
|
66
|
+
will be written to the output channel defined for a match in the selector record.
|
67
|
+
If it does not match the the IP will be written to the output channel defined
|
68
|
+
for a reject in the selector record.
|
69
|
+
|
70
|
+
If the incoming IP does not have any of the keys defined in the selector
|
71
|
+
records the IP will be written to the output channel.
|
72
|
+
=end
|
73
|
+
def do_node_work(args)
|
74
|
+
return false if args.has_key? :completed
|
75
|
+
return true if args.has_key? :start
|
76
|
+
key_match = false
|
77
|
+
|
78
|
+
selectors = @options[:selectors]
|
79
|
+
|
80
|
+
|
81
|
+
multi_ip = args.has_key? :ips
|
82
|
+
ips = multi_ip ? args[:ips] : [args]
|
83
|
+
ips.each do |ip|
|
84
|
+
|
85
|
+
selectors.each do |selector_hash|
|
86
|
+
|
87
|
+
selector_hash.each do |key, h|
|
88
|
+
if ip.has_key? key
|
89
|
+
key_match = true
|
90
|
+
value = ip[key]
|
91
|
+
compare_value = h[:value]
|
92
|
+
comparision = h[:comparison]
|
93
|
+
|
94
|
+
pass = case comparision
|
95
|
+
when NOT_EQUAL_COMPARE
|
96
|
+
compare_value != value
|
97
|
+
when EQUAL_COMPARE
|
98
|
+
compare_value == value
|
99
|
+
when GREATER_THAN
|
100
|
+
value > compare_value
|
101
|
+
when GREATER_THAN_OR_EQUAL
|
102
|
+
value >= compare_value
|
103
|
+
when LESS_THAN
|
104
|
+
value < compare_value
|
105
|
+
when LESS_THAN_OR_EQUAL
|
106
|
+
value <= compare_value
|
107
|
+
when CONTAINS
|
108
|
+
value.to_s.include?(compare_value.to_s)
|
109
|
+
when DOES_NOT_CONTAINS
|
110
|
+
!value.to_s.include?(compare_value.to_s)
|
111
|
+
when STARTS_WITH
|
112
|
+
value.start_with?(compare_value.to_s)
|
113
|
+
when ENDS_WITH
|
114
|
+
value.end_with?(compare_value.to_s)
|
115
|
+
when MATCHES
|
116
|
+
!value.match(compare_value.to_s).nil?
|
117
|
+
else
|
118
|
+
false
|
119
|
+
end
|
120
|
+
output_name = pass ? h[:match] : h[:reject]
|
121
|
+
write_to_output(ip, output_name) if !output.nil?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
write_to_output(args, :output) if !key_match
|
127
|
+
true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Fbp
|
2
|
+
=begin
|
3
|
+
=== Description
|
4
|
+
The Sort node will take its input IP and sort that data according to a set of hash keys that
|
5
|
+
specify which data should be sorted.
|
6
|
+
|
7
|
+
=== Discussion
|
8
|
+
This implementation requires that all of the data to be sorted be available. This is NOT a scalable
|
9
|
+
solution.
|
10
|
+
|
11
|
+
I have read some of the literature on sorting data streams but currently I do not have a ready
|
12
|
+
solution. For now this will have to serve.
|
13
|
+
=end
|
14
|
+
class Sort_node < Node
|
15
|
+
=begin rdoc
|
16
|
+
When creating a new Sort_node instance one can provide an array of
|
17
|
+
symbols which would be used to match keys in an incoming IP. The
|
18
|
+
order of the keys in the array determine the sort order. The
|
19
|
+
first key in the array will be used to sort the data then the
|
20
|
+
subsequent keys if any.
|
21
|
+
=end
|
22
|
+
def initialize(sort_keys = nil)
|
23
|
+
super()
|
24
|
+
@options[:sort_keys] = sort_keys
|
25
|
+
write_to_input({:begin_transaction => true})
|
26
|
+
end
|
27
|
+
=begin rdoc
|
28
|
+
Checks to see if this Sort_node instance has any sort keys set.
|
29
|
+
True will be returned if any keys are set, false otherwise.
|
30
|
+
=end
|
31
|
+
def is_ready_to_run?
|
32
|
+
@options.has_key? :sort_keys
|
33
|
+
end
|
34
|
+
|
35
|
+
=begin rdoc
|
36
|
+
The Sort_node assumes that will will receive an IP with a :ips key
|
37
|
+
which contains all of the IPs that need to be sorted. If the :ips key
|
38
|
+
is not present then the IP is sent unchanged to the down stream node.
|
39
|
+
|
40
|
+
The IPs will be sorted based upon the sort_keys parameter. For each
|
41
|
+
sort_key all of the IPs will be sorted based upon the value in the IP
|
42
|
+
that matches the sort_key. If an IP does not have the sort_key then
|
43
|
+
that IP is excluded from the sort and is disregarded.
|
44
|
+
|
45
|
+
Once the IPs have been sorted the IP has its :ips value replaced by
|
46
|
+
the sorted data and the IP is then sent to the down stream node.
|
47
|
+
=end
|
48
|
+
def do_node_work(args)
|
49
|
+
args.delete :completed
|
50
|
+
ip_array = args[:ips]
|
51
|
+
return super(args) if ip_array.nil?
|
52
|
+
|
53
|
+
sort_keys_array = @options[:sort_keys].respond_to?('each') ? @options[:sort_keys] : [@options[:sort_keys]]
|
54
|
+
|
55
|
+
data_to_sort = Array.new
|
56
|
+
|
57
|
+
ip_array.each {|h| sort_keys_array.each {|k| data_to_sort << h if h.has_key?(k) && !data_to_sort.include?(h)}}
|
58
|
+
sort_keys_array.each {|k| data_to_sort.sort! {|a,b| a[k] <=> b[k]}}
|
59
|
+
data_to_sort.each {|a| write_to_output(a)}
|
60
|
+
|
61
|
+
# Update the input IP with the sorted list of IPs
|
62
|
+
args[:ips] = data_to_sort
|
63
|
+
super(args)
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Fbp
|
2
|
+
=begin
|
3
|
+
=== Description
|
4
|
+
The Text file reader node takes in a file path as an IIP and will open that
|
5
|
+
file and read a line at a time and package that data as an IP and send that
|
6
|
+
data to the downstream node.
|
7
|
+
|
8
|
+
=== Discussion
|
9
|
+
The Test_file_reader_node requires the file to be read exists before running this
|
10
|
+
node.
|
11
|
+
=end
|
12
|
+
class Test_file_reader_node < Node
|
13
|
+
=begin rdoc
|
14
|
+
When creating a new Test_file_reader_node instance one can provide
|
15
|
+
a string which is the full path to the file to be read.
|
16
|
+
=end
|
17
|
+
def initialize(file_name = nil)
|
18
|
+
super()
|
19
|
+
@options[:requires_input] = false
|
20
|
+
@options[:file_name] = file_name if !file_name.nil?
|
21
|
+
@file = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
=begin rdoc
|
25
|
+
Checks to see if this Test_file_reader_node instance has
|
26
|
+
a file_name set. If so then true will be returned otherwise
|
27
|
+
false.
|
28
|
+
=end
|
29
|
+
def is_ready_to_run?()
|
30
|
+
@options.has_key? :file_name
|
31
|
+
end
|
32
|
+
|
33
|
+
=begin
|
34
|
+
Once this instance is executed, it will open the file
|
35
|
+
specified in the :file_name option and read a line
|
36
|
+
at a time. A line is defined as a series of characters
|
37
|
+
up to a return character.
|
38
|
+
|
39
|
+
Once a line is read, an IP is made with that data
|
40
|
+
keys with :data and sent to the down stream node.
|
41
|
+
|
42
|
+
When the EOF is reached the file will be closed.
|
43
|
+
=end
|
44
|
+
def do_node_work(args)
|
45
|
+
|
46
|
+
if args.has_key? :completed
|
47
|
+
@file.close if !@file.nil?
|
48
|
+
@file = nil
|
49
|
+
return false
|
50
|
+
end
|
51
|
+
|
52
|
+
if args.has_key? :continue
|
53
|
+
|
54
|
+
file_line = nil
|
55
|
+
close_file = false
|
56
|
+
@file = File.new(@options[:file_name], 'r') if @file.nil?
|
57
|
+
|
58
|
+
|
59
|
+
begin
|
60
|
+
file_line = @file.gets
|
61
|
+
close_file = true if file_line.nil?
|
62
|
+
close_file = true if @file.eof?
|
63
|
+
rescue
|
64
|
+
close_file = true
|
65
|
+
end
|
66
|
+
|
67
|
+
write_to_output({:data => file_line}) if !file_line.nil?
|
68
|
+
|
69
|
+
if close_file
|
70
|
+
@file.close
|
71
|
+
@file = nil
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Fbp
|
2
|
+
=begin
|
3
|
+
=== Description
|
4
|
+
The Text file writer node takes in a file path as an IIP and will open that
|
5
|
+
file and will take in IPs and write the value of the :data item in the IP to
|
6
|
+
the file.
|
7
|
+
|
8
|
+
== Discussion
|
9
|
+
The Text_file_writer_node does allow for setting the file open mode by having
|
10
|
+
an IIP with a :file_open_mode key specifying the open mode. Setting this value
|
11
|
+
using an IIP allows for setting the open mode to a+ so appending instead of the
|
12
|
+
the default which to overwrite the file using "w"
|
13
|
+
=end
|
14
|
+
class Text_file_writer_node < Node
|
15
|
+
=begin rdoc
|
16
|
+
When creating a new Text_file_writer_node instance one can provide
|
17
|
+
a string which is the full path to the file to be written.
|
18
|
+
=end
|
19
|
+
def initialize(file_name = nil)
|
20
|
+
super()
|
21
|
+
@options[:file_name] = file_name if !file_name.nil?
|
22
|
+
@options[:file_open_mode] = 'w'
|
23
|
+
@file = nil
|
24
|
+
end
|
25
|
+
=begin rdoc
|
26
|
+
Checks to see if this Text_file_writer_node instance has
|
27
|
+
a file_name and file_open_mode set. If so then true will be
|
28
|
+
returned otherwise false.
|
29
|
+
=end
|
30
|
+
def is_ready_to_run?()
|
31
|
+
@options.has_key?(:file_name) && @options.has_key?(:file_open_mode)
|
32
|
+
end
|
33
|
+
=begin rdoc
|
34
|
+
When an IP is received the IP is checked to see if it has a :data key.
|
35
|
+
If it does then that data is written to the file specified by the
|
36
|
+
:file_name option. This will continue until an IP is sent with the
|
37
|
+
:completed key at which will cause the file to be closed.
|
38
|
+
=end
|
39
|
+
def do_node_work(args)
|
40
|
+
|
41
|
+
if args.has_key? :completed
|
42
|
+
@file.close if !@file.nil?
|
43
|
+
@file = nil
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
|
47
|
+
if args.has_key? :data
|
48
|
+
data = args[:data]
|
49
|
+
|
50
|
+
if data.nil?
|
51
|
+
@file.close if !@file.nil?
|
52
|
+
@file = nil
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
@file = File.new(@options[:file_name], @options[:file_open_mode]) if @file.nil?
|
57
|
+
@file.write(data)
|
58
|
+
@file.flush
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/lib/fbp/version.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# Ruby Thread Pool
|
2
|
+
# ================
|
3
|
+
# A thread pool is useful when you wish to do some work in a thread, but do
|
4
|
+
# not know how much work you will be doing in advance. Spawning one thread
|
5
|
+
# for each task is potentially expensive, as threads are not free.
|
6
|
+
#
|
7
|
+
# In this case, it might be more beneficial to start a predefined set of
|
8
|
+
# threads and then hand off work to them as it becomes available. This is
|
9
|
+
# the pure essence of what a thread pool is: an array of threads, all just
|
10
|
+
# waiting to do some work for you!
|
11
|
+
#
|
12
|
+
# Prerequisites
|
13
|
+
# -------------
|
14
|
+
|
15
|
+
# We need the [Queue](http://rdoc.info/stdlib/thread/1.9.2/Queue), as our
|
16
|
+
# thread pool is largely dependent on it. Thanks to this, the implementation
|
17
|
+
# becomes very simple!
|
18
|
+
require 'thread'
|
19
|
+
|
20
|
+
# Public Interface
|
21
|
+
# ----------------
|
22
|
+
|
23
|
+
# `Pool` is our thread pool class. It will allow us to do three operations:
|
24
|
+
#
|
25
|
+
# - `.new(size)` creates a thread pool of a given size
|
26
|
+
# - `#schedule(*args, &job)` schedules a new job to be executed
|
27
|
+
# - `#shutdown` shuts down all threads (after letting them finish working, of course)
|
28
|
+
class Pool
|
29
|
+
|
30
|
+
# ### initialization, or `Pool.new(size)`
|
31
|
+
# Creating a new `Pool` involves a certain amount of work. First, however,
|
32
|
+
# we need to define its’ `size`. It defines how many threads we will have
|
33
|
+
# working internally.
|
34
|
+
#
|
35
|
+
# Which size is best for you is hard to answer. You do not want it to be
|
36
|
+
# too low, as then you won’t be able to do as many things concurrently.
|
37
|
+
# However, if you make it too high Ruby will spend too much time switching
|
38
|
+
# between threads, and that will also degrade performance!
|
39
|
+
def initialize(size)
|
40
|
+
# Before we do anything else, we need to store some information about
|
41
|
+
# our pool. `@size` is useful later, when we want to shut our pool down,
|
42
|
+
# and `@jobs` is the heart of our pool that allows us to schedule work.
|
43
|
+
@size = size
|
44
|
+
@jobs = Queue.new
|
45
|
+
|
46
|
+
# #### Creating our pool of threads
|
47
|
+
# Once preparation is done, it’s time to create our pool of threads.
|
48
|
+
# Each thread store its’ index in a thread-local variable, in case we
|
49
|
+
# need to know which thread a job is executing in later on.
|
50
|
+
@pool = Array.new(@size) do |i|
|
51
|
+
Thread.new do
|
52
|
+
Thread.current[:id] = i
|
53
|
+
|
54
|
+
# We start off by defining a `catch` around our worker loop. This
|
55
|
+
# way we’ve provided a method for graceful shutdown of our threads.
|
56
|
+
# Shutting down is merely a `#schedule { throw :exit }` away!
|
57
|
+
catch(:exit) do
|
58
|
+
# The worker thread life-cycle is very simple. We continuously wait
|
59
|
+
# for tasks to be put into our job `Queue`. If the `Queue` is empty,
|
60
|
+
# we will wait until it’s not.
|
61
|
+
loop do
|
62
|
+
# Once we have a piece of work to be done, we will pull out the
|
63
|
+
# information we need and get to work.
|
64
|
+
job, args = @jobs.pop
|
65
|
+
job.call(*args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# ### Work scheduling
|
73
|
+
|
74
|
+
# To schedule a piece of work to be done is to say to the `Pool` that you
|
75
|
+
# want something done.
|
76
|
+
def schedule(*args, &block)
|
77
|
+
# Your given task will not be run immediately; rather, it will be put
|
78
|
+
# into the work `Queue` and executed once a thread is ready to work.
|
79
|
+
@jobs << [block, args]
|
80
|
+
end
|
81
|
+
|
82
|
+
# ### Graceful shutdown
|
83
|
+
|
84
|
+
# If you ever wish to close down your application, I took the liberty of
|
85
|
+
# making it easy for you to wait for any currently executing jobs to finish
|
86
|
+
# before you exit.
|
87
|
+
def shutdown
|
88
|
+
# A graceful shutdown involves threads exiting cleanly themselves, and
|
89
|
+
# since we’ve defined a `catch`-handler around the threads’ worker loop
|
90
|
+
# it is simply a matter of throwing `:exit`. Thus, if we throw one `:exit`
|
91
|
+
# for each thread in our pool, they will all exit eventually!
|
92
|
+
@size.times do
|
93
|
+
schedule { throw :exit }
|
94
|
+
end
|
95
|
+
|
96
|
+
# And now one final thing: wait for our `throw :exit` jobs to be run on
|
97
|
+
# all our worker threads. This call will not return until all worker threads
|
98
|
+
# have exited.
|
99
|
+
@pool.map(&:join)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Demonstration
|
104
|
+
# -------------
|
105
|
+
# Running this file will display how the thread pool works.
|
106
|
+
if $0 == __FILE__
|
107
|
+
# - First, we create a new thread pool with a size of 10. This number is
|
108
|
+
# lower than our planned amount of work, to show that threads do not
|
109
|
+
# exit once they have finished a task.
|
110
|
+
p = Pool.new(10)
|
111
|
+
|
112
|
+
# - Next we simulate some workload by scheduling a large amount of work
|
113
|
+
# to be done. The actual time taken for each job is randomized. This
|
114
|
+
# is to demonstrate that even if two tasks are scheduled approximately
|
115
|
+
# at the same time, the one that takes less time to execute is likely
|
116
|
+
# to finish before the other one.
|
117
|
+
20.times do |i|
|
118
|
+
p.schedule do
|
119
|
+
sleep rand(4) + 2
|
120
|
+
puts "Job #{i} finished by thread #{Thread.current[:id]}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# - Finally, register an `at_exit`-hook that will wait for our thread pool
|
125
|
+
# to properly shut down before allowing our script to completely exit.
|
126
|
+
at_exit { p.shutdown }
|
127
|
+
end
|
128
|
+
|
129
|
+
# License (X11 License)
|
130
|
+
# =====================
|
131
|
+
#
|
132
|
+
# Copyright (c) 2012, Kim Burgestrand <kim@burgestrand.se>
|
133
|
+
#
|
134
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
135
|
+
# of this software and associated documentation files (the "Software"), to deal
|
136
|
+
# in the Software without restriction, including without limitation the rights
|
137
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
138
|
+
# copies of the Software, and to permit persons to whom the Software is
|
139
|
+
# furnished to do so, subject to the following conditions:
|
140
|
+
#
|
141
|
+
# The above copyright notice and this permission notice shall be included in
|
142
|
+
# all copies or substantial portions of the Software.
|
143
|
+
#
|
144
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
145
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
146
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
147
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
148
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
149
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
150
|
+
# SOFTWARE.
|