fairy 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.
- data/LICENSE +674 -0
- data/Makefile +116 -0
- data/README +15 -0
- data/bin/fairy +582 -0
- data/bin/fairy-cat +74 -0
- data/bin/fairy-cp +128 -0
- data/bin/fairy-rm +122 -0
- data/bin/subcmd/controller +41 -0
- data/bin/subcmd/inspector +81 -0
- data/bin/subcmd/master +43 -0
- data/bin/subcmd/node +47 -0
- data/bin/subcmd/processor +54 -0
- data/doc/programming-interface.html +240 -0
- data/doc/programming-interface.rd +300 -0
- data/etc/fairy.conf.tmpl +118 -0
- data/ext/simple_hash/extconf.rb +4 -0
- data/ext/simple_hash/simple_hash.c +42 -0
- data/fairy.gemspec +60 -0
- data/lib/fairy/client/addins.rb +20 -0
- data/lib/fairy/client/barrier.rb +29 -0
- data/lib/fairy/client/basic-group-by.rb +52 -0
- data/lib/fairy/client/cat.rb +41 -0
- data/lib/fairy/client/direct-product.rb +51 -0
- data/lib/fairy/client/equijoin.rb +79 -0
- data/lib/fairy/client/exec.rb +54 -0
- data/lib/fairy/client/filter.rb +62 -0
- data/lib/fairy/client/find.rb +35 -0
- data/lib/fairy/client/group-by.rb +194 -0
- data/lib/fairy/client/here.rb +84 -0
- data/lib/fairy/client/inject.rb +70 -0
- data/lib/fairy/client/input-file.rb +53 -0
- data/lib/fairy/client/input-iota.rb +49 -0
- data/lib/fairy/client/input-local-file.rb +188 -0
- data/lib/fairy/client/input-varray.rb +30 -0
- data/lib/fairy/client/input.rb +42 -0
- data/lib/fairy/client/io-filter.rb +26 -0
- data/lib/fairy/client/junction.rb +31 -0
- data/lib/fairy/client/map.rb +34 -0
- data/lib/fairy/client/merge-group-by.rb +71 -0
- data/lib/fairy/client/output-file.rb +64 -0
- data/lib/fairy/client/output-local-file.rb +60 -0
- data/lib/fairy/client/output-null.rb +47 -0
- data/lib/fairy/client/output-varray.rb +50 -0
- data/lib/fairy/client/output.rb +29 -0
- data/lib/fairy/client/roma-put.rb +62 -0
- data/lib/fairy/client/roma.rb +156 -0
- data/lib/fairy/client/seg-join.rb +61 -0
- data/lib/fairy/client/seg-map.rb +78 -0
- data/lib/fairy/client/seg-shuffle.rb +35 -0
- data/lib/fairy/client/seg-split.rb +27 -0
- data/lib/fairy/client/seg-zip.rb +60 -0
- data/lib/fairy/client/select.rb +38 -0
- data/lib/fairy/client/sort.rb +48 -0
- data/lib/fairy/client/sort18.rb +56 -0
- data/lib/fairy/client/sort19.rb +61 -0
- data/lib/fairy/client/there.rb +47 -0
- data/lib/fairy/client/top_n_into_roma.rb +34 -0
- data/lib/fairy/client/wc.rb +92 -0
- data/lib/fairy/controller.rb +1103 -0
- data/lib/fairy/logger.rb +107 -0
- data/lib/fairy/master/addins.rb +20 -0
- data/lib/fairy/master/atom.rb +17 -0
- data/lib/fairy/master/c-barrier.rb +283 -0
- data/lib/fairy/master/c-basic-group-by.rb +250 -0
- data/lib/fairy/master/c-cat.rb +159 -0
- data/lib/fairy/master/c-direct-product.rb +203 -0
- data/lib/fairy/master/c-exec.rb +68 -0
- data/lib/fairy/master/c-filter.rb +422 -0
- data/lib/fairy/master/c-find.rb +138 -0
- data/lib/fairy/master/c-group-by.rb +64 -0
- data/lib/fairy/master/c-here.rb +80 -0
- data/lib/fairy/master/c-inject.rb +119 -0
- data/lib/fairy/master/c-input-file.rb +46 -0
- data/lib/fairy/master/c-input-iota.rb +66 -0
- data/lib/fairy/master/c-input-local-file.rb +117 -0
- data/lib/fairy/master/c-input-varray.rb +53 -0
- data/lib/fairy/master/c-input.rb +24 -0
- data/lib/fairy/master/c-inputtable.rb +31 -0
- data/lib/fairy/master/c-inputtable18.rb +36 -0
- data/lib/fairy/master/c-inputtable19.rb +35 -0
- data/lib/fairy/master/c-io-filter.rb +28 -0
- data/lib/fairy/master/c-junction.rb +54 -0
- data/lib/fairy/master/c-map.rb +27 -0
- data/lib/fairy/master/c-merge-group-by.rb +241 -0
- data/lib/fairy/master/c-output-file.rb +84 -0
- data/lib/fairy/master/c-output-local-file.rb +19 -0
- data/lib/fairy/master/c-output-null.rb +45 -0
- data/lib/fairy/master/c-output-varray.rb +57 -0
- data/lib/fairy/master/c-output.rb +20 -0
- data/lib/fairy/master/c-seg-join.rb +141 -0
- data/lib/fairy/master/c-seg-map.rb +26 -0
- data/lib/fairy/master/c-seg-shuffle.rb +87 -0
- data/lib/fairy/master/c-seg-split.rb +110 -0
- data/lib/fairy/master/c-seg-zip.rb +132 -0
- data/lib/fairy/master/c-select.rb +27 -0
- data/lib/fairy/master/c-sort.rb +108 -0
- data/lib/fairy/master/c-there.rb +57 -0
- data/lib/fairy/master/c-wc.rb +232 -0
- data/lib/fairy/master/job-interpriter.rb +19 -0
- data/lib/fairy/master/scheduler.rb +24 -0
- data/lib/fairy/master.rb +329 -0
- data/lib/fairy/node/addins.rb +19 -0
- data/lib/fairy/node/p-barrier.rb +95 -0
- data/lib/fairy/node/p-basic-group-by.rb +252 -0
- data/lib/fairy/node/p-direct-product.rb +153 -0
- data/lib/fairy/node/p-exec.rb +30 -0
- data/lib/fairy/node/p-filter.rb +363 -0
- data/lib/fairy/node/p-find.rb +111 -0
- data/lib/fairy/node/p-group-by.rb +1534 -0
- data/lib/fairy/node/p-here.rb +21 -0
- data/lib/fairy/node/p-identity.rb +24 -0
- data/lib/fairy/node/p-inject.rb +127 -0
- data/lib/fairy/node/p-input-file.rb +108 -0
- data/lib/fairy/node/p-input-iota.rb +39 -0
- data/lib/fairy/node/p-input-local-file.rb +61 -0
- data/lib/fairy/node/p-input-varray.rb +26 -0
- data/lib/fairy/node/p-io-filter.rb +28 -0
- data/lib/fairy/node/p-map.rb +40 -0
- data/lib/fairy/node/p-merger-group-by.rb +48 -0
- data/lib/fairy/node/p-output-file.rb +104 -0
- data/lib/fairy/node/p-output-local-file.rb +14 -0
- data/lib/fairy/node/p-output-null.rb +32 -0
- data/lib/fairy/node/p-output-varray.rb +41 -0
- data/lib/fairy/node/p-seg-join.rb +82 -0
- data/lib/fairy/node/p-seg-map.rb +34 -0
- data/lib/fairy/node/p-seg-split.rb +61 -0
- data/lib/fairy/node/p-seg-zip.rb +79 -0
- data/lib/fairy/node/p-select.rb +40 -0
- data/lib/fairy/node/p-single-exportable.rb +90 -0
- data/lib/fairy/node/p-sort.rb +195 -0
- data/lib/fairy/node/p-task.rb +113 -0
- data/lib/fairy/node/p-there.rb +44 -0
- data/lib/fairy/node/p-wc.rb +266 -0
- data/lib/fairy/node.rb +187 -0
- data/lib/fairy/processor.rb +510 -0
- data/lib/fairy/share/base-app.rb +114 -0
- data/lib/fairy/share/block-source.rb +234 -0
- data/lib/fairy/share/conf.rb +396 -0
- data/lib/fairy/share/debug.rb +21 -0
- data/lib/fairy/share/encoding.rb +17 -0
- data/lib/fairy/share/fast-tempfile.rb +93 -0
- data/lib/fairy/share/file-place.rb +176 -0
- data/lib/fairy/share/hash-1.rb +20 -0
- data/lib/fairy/share/hash-md5.rb +28 -0
- data/lib/fairy/share/hash-murmur.rb +69 -0
- data/lib/fairy/share/hash-rb18.rb +20 -0
- data/lib/fairy/share/hash-simple-hash.rb +28 -0
- data/lib/fairy/share/inspector.rb +16 -0
- data/lib/fairy/share/lc/exceptions.rb +82 -0
- data/lib/fairy/share/lc/ja/exceptions.rb +81 -0
- data/lib/fairy/share/locale.rb +17 -0
- data/lib/fairy/share/log.rb +215 -0
- data/lib/fairy/share/pool-dictionary.rb +53 -0
- data/lib/fairy/share/port-marshaled-queue.rb +347 -0
- data/lib/fairy/share/port.rb +1697 -0
- data/lib/fairy/share/reference.rb +45 -0
- data/lib/fairy/share/stdout.rb +56 -0
- data/lib/fairy/share/tr.rb +16 -0
- data/lib/fairy/share/varray.rb +147 -0
- data/lib/fairy/share/vfile.rb +183 -0
- data/lib/fairy/version.rb +8 -0
- data/lib/fairy.rb +206 -0
- data/sample/grep.rb +46 -0
- data/sample/ping.rb +19 -0
- data/sample/sort.rb +102 -0
- data/sample/wordcount.rb +61 -0
- data/spec/README +12 -0
- data/spec/fairy1_spec.rb +31 -0
- data/spec/fairy2_spec.rb +42 -0
- data/spec/fairy3_spec.rb +126 -0
- data/spec/fairy4_spec.rb +63 -0
- data/spec/fairy5_spec.rb +45 -0
- data/spec/fairy6_spec.rb +52 -0
- data/spec/fairy7_spec.rb +58 -0
- data/spec/fairy8_spec.rb +48 -0
- data/spec/mkdat.rb +148 -0
- data/spec/run_all.sh +65 -0
- data/test/testc.rb +7111 -0
- data/tools/cap_recipe/Capfile +144 -0
- data/tools/cap_recipe/cluster.yml.sample +14 -0
- data/tools/fairy_perf_graph.rb +444 -0
- data/tools/git-tag +44 -0
- data/tools/log-analysis.rb +62 -0
- data/tools/svn-ls-diff +38 -0
- data/tools/svn-tags +37 -0
- metadata +298 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2007-2010 Rakuten, Inc.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
require "delegate"
|
|
7
|
+
|
|
8
|
+
require "thread"
|
|
9
|
+
|
|
10
|
+
require "fairy/master/c-filter"
|
|
11
|
+
require "fairy/master/c-io-filter"
|
|
12
|
+
|
|
13
|
+
module Fairy
|
|
14
|
+
class CCat<CIOFilter
|
|
15
|
+
Controller.def_export self
|
|
16
|
+
|
|
17
|
+
DeepConnect.def_single_method_spec(self, "REF new(REF, VAL, VAL)")
|
|
18
|
+
|
|
19
|
+
def initialize(controller, opts, others)
|
|
20
|
+
super
|
|
21
|
+
|
|
22
|
+
@others = others
|
|
23
|
+
@export_node_pairs_queues = nil
|
|
24
|
+
@export_node_pairs_queues_mutex = Mutex.new
|
|
25
|
+
@export_node_pairs_queues_cv = ConditionVariable.new
|
|
26
|
+
|
|
27
|
+
@main_precat = CPreCat.new(controller, opts)
|
|
28
|
+
|
|
29
|
+
@others_precat = @others.map{|o|
|
|
30
|
+
precat = CPreCat.new(controller, opts)
|
|
31
|
+
precat.input = o
|
|
32
|
+
precat
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def node_class_name
|
|
37
|
+
"PIdentity"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def njob_creation_params
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def input=(input)
|
|
45
|
+
@main_precat.input = input
|
|
46
|
+
# super(@main_precat)
|
|
47
|
+
start_create_nodes
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create_nodes
|
|
51
|
+
begin
|
|
52
|
+
no = 0
|
|
53
|
+
[@main_precat, *@others_precat].each do |input|
|
|
54
|
+
@input = input
|
|
55
|
+
input.output = @input
|
|
56
|
+
@controller.assign_ntasks(self, @create_node_mutex) do
|
|
57
|
+
|ntask, mapper, opts={}|
|
|
58
|
+
# njob = create_and_add_node(processor, mapper)
|
|
59
|
+
njob = create_node(ntask) {|node|
|
|
60
|
+
if opts[:init_njob]
|
|
61
|
+
opts[:init_njob].call(node)
|
|
62
|
+
end
|
|
63
|
+
mapper.bind_input(node)
|
|
64
|
+
node.no = no
|
|
65
|
+
}
|
|
66
|
+
no += 1
|
|
67
|
+
njob
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
rescue BreakCreateNode
|
|
71
|
+
# do nothing
|
|
72
|
+
Log::debug self, "BREAK CREATE NODE: #{self}"
|
|
73
|
+
|
|
74
|
+
rescue AbortCreateNode
|
|
75
|
+
Log::debug self, "Abort CREATE NODE: #{self}"
|
|
76
|
+
# do nothing
|
|
77
|
+
|
|
78
|
+
rescue ERR::NodeNotArrived
|
|
79
|
+
Log::debug self, "NODE NOT ARRIVED: #{file}"
|
|
80
|
+
begin
|
|
81
|
+
handle_exception($!)
|
|
82
|
+
rescue
|
|
83
|
+
Log::debug_exception(self)
|
|
84
|
+
end
|
|
85
|
+
Log::debug self, "NODE NOT ARRIVED2: #{file}"
|
|
86
|
+
raise
|
|
87
|
+
|
|
88
|
+
rescue ERR::CantExecSubcmd
|
|
89
|
+
begin
|
|
90
|
+
handle_exception($!)
|
|
91
|
+
rescue
|
|
92
|
+
Log::debug_exception(self)
|
|
93
|
+
end
|
|
94
|
+
Log::debug self, "CANT EXEC SUBCOMMAND: #{self}"
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
rescue ERR::CantExecSubcmd
|
|
98
|
+
begin
|
|
99
|
+
handle_exception($!)
|
|
100
|
+
rescue
|
|
101
|
+
Log::debug_exception(self)
|
|
102
|
+
end
|
|
103
|
+
Log::debug self, "CANT EXEC SUBCOMMAND: #{self}"
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
rescue Exception
|
|
107
|
+
Log::warn_exception(self)
|
|
108
|
+
raise
|
|
109
|
+
ensure
|
|
110
|
+
Log::debug self, "CREATE_NODES: #{self}.number_of_nodes=#{no}"
|
|
111
|
+
add_node(nil)
|
|
112
|
+
self.number_of_nodes = no
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# def start_get_exports
|
|
117
|
+
# @export_node_pairs_queues = [@input, *@others].collect{|input|
|
|
118
|
+
# export_node_pairs = Queue.new
|
|
119
|
+
# Thread.start do
|
|
120
|
+
# input.each_export do |*export_node_pair|
|
|
121
|
+
# export_node_pairs.push export_node_pair
|
|
122
|
+
# end
|
|
123
|
+
# export_node_pairs.push nil
|
|
124
|
+
# end
|
|
125
|
+
# export_node_pairs
|
|
126
|
+
# }
|
|
127
|
+
# @export_node_pairs_queues_cv.broadcast
|
|
128
|
+
# end
|
|
129
|
+
|
|
130
|
+
# def each_export(&block)
|
|
131
|
+
# @export_node_pairs_queues_mutex.synchronize do
|
|
132
|
+
# while !@export_node_pairs_queues
|
|
133
|
+
# @export_node_pairs_queues_cv.wait(@export_node_pairs_queues_mutex)
|
|
134
|
+
# end
|
|
135
|
+
# end
|
|
136
|
+
|
|
137
|
+
# for export_node_pairs in @export_node_pairs_queues
|
|
138
|
+
# while pair = export_node_pairs.pop
|
|
139
|
+
# block.call *pair
|
|
140
|
+
# end
|
|
141
|
+
# end
|
|
142
|
+
# end
|
|
143
|
+
|
|
144
|
+
class CPreCat<CIOFilter
|
|
145
|
+
def initialize(controller, opts)
|
|
146
|
+
super
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def node_class_name
|
|
150
|
+
"PIdentity"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def njob_creation_params
|
|
154
|
+
[]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2007-2010 Rakuten, Inc.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
require "forwardable"
|
|
7
|
+
|
|
8
|
+
require "deep-connect"
|
|
9
|
+
|
|
10
|
+
require "fairy/master/c-io-filter"
|
|
11
|
+
|
|
12
|
+
module Fairy
|
|
13
|
+
class CDirectProduct<CIOFilter
|
|
14
|
+
extend Forwardable
|
|
15
|
+
|
|
16
|
+
Controller.def_export self
|
|
17
|
+
|
|
18
|
+
DeepConnect.def_single_method_spec(self, "REF new(REF, VAL, VAL, REF)")
|
|
19
|
+
|
|
20
|
+
def initialize(controller, opts, others, block_source)
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
@others = others
|
|
24
|
+
@block_source = block_source
|
|
25
|
+
|
|
26
|
+
@main_prefilter = CPreFilter.new(@controller, @opts, block_source)
|
|
27
|
+
@main_prefilter.main = self
|
|
28
|
+
@other_prefilters = []
|
|
29
|
+
@others.each do |other|
|
|
30
|
+
prefilter = CPreFilter.new(@controller, @opts, block_source)
|
|
31
|
+
prefilter.main = self
|
|
32
|
+
@other_prefilters.push prefilter
|
|
33
|
+
end
|
|
34
|
+
@postfilter = CPostFilter.new(@controller, @opts, block_source)
|
|
35
|
+
|
|
36
|
+
@prefilter_no_nodes = {}
|
|
37
|
+
@prefilter_no_nodes_mutex = Mutex.new
|
|
38
|
+
@prefilter_no_nodes_cv = ConditionVariable.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attr_reader :other_prefilters
|
|
42
|
+
|
|
43
|
+
def all_prefilters
|
|
44
|
+
[@main_prefilter, *@other_prefilters]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def njob_creation_params
|
|
48
|
+
[@block_source]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def each_assigned_filter(&block)
|
|
52
|
+
@postfilter.each_assigned_filter &block
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def input=(other)
|
|
56
|
+
@main_prefilter.input = other
|
|
57
|
+
@others.zip(@other_prefilters) do |o, prefilter|
|
|
58
|
+
prefilter.input = o
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@postfilter.input = @main_prefilter
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def update_prefilter_no_nodes(prefilter)
|
|
65
|
+
@prefilter_no_nodes_mutex.synchronize do
|
|
66
|
+
@prefilter_no_nodes[prefilter] = prefilter.number_of_nodes
|
|
67
|
+
@prefilter_no_nodes_cv.broadcast
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def no_of_exports_for_prefilter(prefilter)
|
|
72
|
+
all_prefilters.reject{|f| prefilter==f}.inject(1){|dp, f|
|
|
73
|
+
@prefilter_no_nodes_mutex.synchronize do
|
|
74
|
+
while (v = @prefilter_no_nodes[f]).nil?
|
|
75
|
+
@prefilter_no_nodes_cv.wait(@prefilter_no_nodes_mutex)
|
|
76
|
+
end
|
|
77
|
+
dp *= v
|
|
78
|
+
end
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 呼ばれない
|
|
83
|
+
# def start_create_nodes
|
|
84
|
+
# @main_prefilter.start_create_nodes
|
|
85
|
+
# end
|
|
86
|
+
|
|
87
|
+
class CPreFilter<CIOFilter
|
|
88
|
+
Controller.def_export self
|
|
89
|
+
|
|
90
|
+
def initialize(controller, opts, block_source)
|
|
91
|
+
super
|
|
92
|
+
@block_source = block_source
|
|
93
|
+
|
|
94
|
+
@no = 0
|
|
95
|
+
@exports = {}
|
|
96
|
+
@exports_mutex = Mutex.new
|
|
97
|
+
# @exports_cv = ConditionVariable.new
|
|
98
|
+
|
|
99
|
+
@products = nil
|
|
100
|
+
@products_mutex = Mutex.new
|
|
101
|
+
@products_cv = ConditionVariable.new
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def main=(main)
|
|
105
|
+
@main = main
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def node_class_name
|
|
109
|
+
"PDirectProduct::PPreFilter"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def njob_creation_params
|
|
113
|
+
[@block_source]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def number_of_nodes=(no)
|
|
117
|
+
super
|
|
118
|
+
@main.update_prefilter_no_nodes(self)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def number_of_exports
|
|
122
|
+
@main.no_of_exports_for_prefilter(self)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def start_create_nodes
|
|
126
|
+
Log::debug self, "START_CREATE_NODES: #{self}"
|
|
127
|
+
@main.other_prefilters.each do |other|
|
|
128
|
+
Thread.start do
|
|
129
|
+
other.each_assigned_filter do |input_filter|
|
|
130
|
+
exp = input_filter.start_export
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
super
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def each_assigned_filter(&block)
|
|
138
|
+
Thread.start do
|
|
139
|
+
@main.other_prefilters.each do |p|
|
|
140
|
+
p.each_node do |n|
|
|
141
|
+
@exports_mutex.synchronize do
|
|
142
|
+
@exports[n] = n.exports.dc_dup
|
|
143
|
+
# @exports_cv.broadcast
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
@products_mutex.synchronize do
|
|
148
|
+
@products = nodes.product(*@main.other_prefilters.collect{|p| p.nodes})
|
|
149
|
+
@products_cv.broadcast
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
super
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# main prefilter 用
|
|
157
|
+
def each_export_by(njob, mapper, &block)
|
|
158
|
+
@exports_mutex.synchronize do
|
|
159
|
+
@exports[njob] = njob.exports.dc_dup
|
|
160
|
+
# @exports_cv.broadcast
|
|
161
|
+
end
|
|
162
|
+
@products_mutex.synchronize do
|
|
163
|
+
while !@products
|
|
164
|
+
@products_cv.wait(@products_mutex)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
post_njob_no = -1
|
|
168
|
+
@products.each do |main_njob, *others_njobs|
|
|
169
|
+
post_njob_no += 1
|
|
170
|
+
next if main_njob != njob
|
|
171
|
+
@others_njobs = others_njobs
|
|
172
|
+
|
|
173
|
+
block.call(@exports[main_njob].shift,
|
|
174
|
+
:init_njob => proc{|njob|
|
|
175
|
+
njob.no = post_njob_no
|
|
176
|
+
njob.other_inputs = others_njobs.collect{|n| @exports[n].shift}})
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def bind_export(exp, imp)
|
|
182
|
+
# do nothing
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class CPostFilter<CIOFilter
|
|
187
|
+
Controller.def_export self
|
|
188
|
+
|
|
189
|
+
def initialize(controller, opts, block_source)
|
|
190
|
+
super
|
|
191
|
+
@block_source = block_source
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def node_class_name
|
|
195
|
+
"PDirectProduct::PPostFilter"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def njob_creation_params
|
|
199
|
+
[@block_source]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
require "fairy/master/c-input"
|
|
6
|
+
require "fairy/share/vfile"
|
|
7
|
+
|
|
8
|
+
module Fairy
|
|
9
|
+
|
|
10
|
+
class CExec<CInput
|
|
11
|
+
Controller.def_export self
|
|
12
|
+
|
|
13
|
+
URI_REGEXP = /:\/\//
|
|
14
|
+
|
|
15
|
+
def node_class_name
|
|
16
|
+
"PExec"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def start(vf)
|
|
20
|
+
@vfile = vf
|
|
21
|
+
@cfile_place = CFilePlace.new(@vfile)
|
|
22
|
+
|
|
23
|
+
start_create_nodes
|
|
24
|
+
end
|
|
25
|
+
DeepConnect.def_method_spec(self, "REF start(DVAL)")
|
|
26
|
+
|
|
27
|
+
def input
|
|
28
|
+
@cfile_place
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# def create_and_start_nodes
|
|
33
|
+
# begin
|
|
34
|
+
# no = 0
|
|
35
|
+
# for node_spec in @vfile
|
|
36
|
+
# @create_node_mutex.synchronize do
|
|
37
|
+
# no += 1
|
|
38
|
+
# Log::debug self, "NO: #{no}"
|
|
39
|
+
# host = "localhost"
|
|
40
|
+
# path = node_spec
|
|
41
|
+
# if URI_REGEXP =~ node_spec
|
|
42
|
+
# uri = URI(node_spec)
|
|
43
|
+
# host = uri.host
|
|
44
|
+
# if /^\[([0-9a-f.:]*)\]$/ =~ host
|
|
45
|
+
# host = $1
|
|
46
|
+
# end
|
|
47
|
+
# path = uri.path
|
|
48
|
+
# end
|
|
49
|
+
|
|
50
|
+
# @controller.assign_input_processor(self, host) do |processor|
|
|
51
|
+
# njob = create_node(processor)
|
|
52
|
+
# njob.start(node_spec)
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
# end
|
|
56
|
+
# rescue BreakCreateNode
|
|
57
|
+
# # do nothing
|
|
58
|
+
# Log::debug self, "BREAK CREATE NODE: #{self}"
|
|
59
|
+
# rescue Exception
|
|
60
|
+
# Log::warn_exception(self)
|
|
61
|
+
# raise
|
|
62
|
+
# ensure
|
|
63
|
+
# Log::debug self, "CREATE_NODES: #{self}.number_of_nodes=#{no}"
|
|
64
|
+
# self.number_of_nodes = no
|
|
65
|
+
# end
|
|
66
|
+
# end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2007-2010 Rakuten, Inc.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Fairy
|
|
7
|
+
class CFilter
|
|
8
|
+
Controller.def_export self
|
|
9
|
+
|
|
10
|
+
@@watch_status = false
|
|
11
|
+
def self.watch_status
|
|
12
|
+
@@watch_status
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.watch_status=(val)
|
|
16
|
+
@@watch_status=val
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
DeepConnect.def_single_method_spec(self, "REF new(REF, VAL, *DEFAULT)")
|
|
20
|
+
|
|
21
|
+
def initialize(controller, opts, *rests)
|
|
22
|
+
Log::info self, "CREATE BJOB: #{self.class}"
|
|
23
|
+
@controller = controller
|
|
24
|
+
|
|
25
|
+
@opts = opts
|
|
26
|
+
@opts = {} unless @opts
|
|
27
|
+
|
|
28
|
+
@job_pool_dict = PoolDictionary.new
|
|
29
|
+
|
|
30
|
+
@number_of_nodes = nil
|
|
31
|
+
# @number_of_nodes_mutex = Mutex.new
|
|
32
|
+
# @number_of_nodes_cv = ConditionVariable.new
|
|
33
|
+
|
|
34
|
+
@nodes = []
|
|
35
|
+
@nodes_for_next_filters = []
|
|
36
|
+
@nodes_mutex = Mutex.new
|
|
37
|
+
@nodes_cv = ConditionVariable.new
|
|
38
|
+
|
|
39
|
+
@nodes_status = {}
|
|
40
|
+
@nodes_status_mutex = Mutex.new
|
|
41
|
+
@nodes_status_cv = ConditionVariable.new
|
|
42
|
+
|
|
43
|
+
@controller.register_bjob(self)
|
|
44
|
+
|
|
45
|
+
@create_node_thread = nil
|
|
46
|
+
# gbreakのときに安全に@create_node_threadスレッドをとめるため
|
|
47
|
+
@create_node_mutex = Mutex.new
|
|
48
|
+
|
|
49
|
+
@context = Context.new(self)
|
|
50
|
+
|
|
51
|
+
start_watch_node_status if watch_status?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def input
|
|
55
|
+
ERR::Raise ERR::INTERNAL::ShouldDefineSubclass
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def postmapping_policy
|
|
59
|
+
@opts[:postmapping_policy] || CONF.POSTMAPPING_POLICY
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# Pool Variables:
|
|
64
|
+
# (JP: プール変数)
|
|
65
|
+
#
|
|
66
|
+
def pool_dict
|
|
67
|
+
@controller.pool_dict
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def job_pool_dict
|
|
71
|
+
@job_pool_dict
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def def_job_pool_variable(vname, value = nil)
|
|
75
|
+
@job_pool_dict.def_variable(vname, value)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def job_pool_variable(vname, *value)
|
|
79
|
+
if value.empty?
|
|
80
|
+
@job_pool_dict[vname]
|
|
81
|
+
else
|
|
82
|
+
@job_pool_dict[vname] = value
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# Njob Methods:
|
|
89
|
+
#
|
|
90
|
+
def number_of_nodes=(no)
|
|
91
|
+
#puts "#{self}.number_of_nodes=#{no}"
|
|
92
|
+
# @number_of_nodes_mutex.synchronize do
|
|
93
|
+
@nodes_mutex.synchronize do
|
|
94
|
+
@number_of_nodes = no
|
|
95
|
+
# @number_of_nodes_cv.broadcast
|
|
96
|
+
@nodes_cv.broadcast
|
|
97
|
+
@nodes_status_cv.broadcast
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def nodes
|
|
102
|
+
@nodes_mutex.synchronize do
|
|
103
|
+
@nodes
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def add_node(node)
|
|
108
|
+
@nodes_mutex.synchronize do
|
|
109
|
+
unless node
|
|
110
|
+
@nodes_for_next_filters.push nil
|
|
111
|
+
@nodes_cv.broadcast
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# node.no = node.input.no
|
|
116
|
+
@nodes[node.no] = node
|
|
117
|
+
@nodes_for_next_filters.push node
|
|
118
|
+
@nodes_cv.broadcast
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def each_node(flag = nil, &block)
|
|
123
|
+
if flag == :exist_only
|
|
124
|
+
return each_node_exist_only &block
|
|
125
|
+
end
|
|
126
|
+
@nodes_mutex.synchronize do
|
|
127
|
+
idx = 0
|
|
128
|
+
while !@number_of_nodes || idx < @number_of_nodes
|
|
129
|
+
unless @nodes[idx]
|
|
130
|
+
@nodes_cv.wait(@nodes_mutex)
|
|
131
|
+
next
|
|
132
|
+
end
|
|
133
|
+
begin
|
|
134
|
+
@nodes_mutex.unlock
|
|
135
|
+
block.call @nodes[idx]
|
|
136
|
+
ensure
|
|
137
|
+
@nodes_mutex.lock
|
|
138
|
+
end
|
|
139
|
+
idx +=1
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def each_node_exist_only(&block)
|
|
145
|
+
nodes = @nodes_mutex.synchronize{@nodes.dup}
|
|
146
|
+
nodes.each &block
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# def each_export(&block)
|
|
150
|
+
# each_node do |node|
|
|
151
|
+
# exp = node.export
|
|
152
|
+
# block.call exp, node
|
|
153
|
+
# node.export.output_no_import = 1
|
|
154
|
+
# end
|
|
155
|
+
# end
|
|
156
|
+
|
|
157
|
+
def number_of_nodes
|
|
158
|
+
# @number_of_nodes_mutex.synchronize do
|
|
159
|
+
@nodes_mutex.synchronize do
|
|
160
|
+
while !@number_of_nodes
|
|
161
|
+
# @number_of_nodes_cv.wait(@number_of_nodes_mutex)
|
|
162
|
+
@nodes_cv.wait(@nodes_mutex)
|
|
163
|
+
end
|
|
164
|
+
@number_of_nodes
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#
|
|
169
|
+
# Njob creation methods
|
|
170
|
+
#
|
|
171
|
+
def start_create_nodes
|
|
172
|
+
@create_node_thread = Thread.start{
|
|
173
|
+
Log::debug self, "START_CREATE_NODES: START #{self}"
|
|
174
|
+
create_nodes
|
|
175
|
+
Log::debug self, "START_CREATE_NODES: END #{self}"
|
|
176
|
+
}
|
|
177
|
+
nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def create_nodes
|
|
181
|
+
begin
|
|
182
|
+
no = 0
|
|
183
|
+
ret = nil
|
|
184
|
+
@controller.assign_ntasks(self, @create_node_mutex) do
|
|
185
|
+
|ntask, mapper, opts={}|
|
|
186
|
+
njob = create_and_add_node(ntask, mapper, opts)
|
|
187
|
+
no += 1
|
|
188
|
+
njob
|
|
189
|
+
end
|
|
190
|
+
add_node(nil)
|
|
191
|
+
Log::debug self, "CREATE_NODES: #{self}.number_of_nodes=#{no}"
|
|
192
|
+
self.number_of_nodes = no
|
|
193
|
+
|
|
194
|
+
rescue BreakCreateNode
|
|
195
|
+
Log::debug self, "BREAK CREATE NODE: #{self}"
|
|
196
|
+
add_node(nil)
|
|
197
|
+
Log::debug self, "CREATE_NODES: #{self}.number_of_nodes=#{no}"
|
|
198
|
+
self.number_of_nodes = no
|
|
199
|
+
|
|
200
|
+
rescue AbortCreateNode
|
|
201
|
+
Log::debug self, "Abort CREATE NODE: #{self}"
|
|
202
|
+
# do nothing
|
|
203
|
+
|
|
204
|
+
rescue ERR::NodeNotArrived
|
|
205
|
+
Log::debug self, "NODE NOT ARRIVED: #{self}"
|
|
206
|
+
begin
|
|
207
|
+
handle_exception($!)
|
|
208
|
+
rescue
|
|
209
|
+
Log::debug_exception(self)
|
|
210
|
+
end
|
|
211
|
+
Log::debug self, "NODE NOT ARRIVED2: #{self}"
|
|
212
|
+
raise
|
|
213
|
+
|
|
214
|
+
rescue ERR::CantExecSubcmd
|
|
215
|
+
begin
|
|
216
|
+
handle_exception($!)
|
|
217
|
+
rescue
|
|
218
|
+
Log::debug_exception(self)
|
|
219
|
+
end
|
|
220
|
+
Log::debug self, "CANT EXEC SUBCOMMAND: #{self}"
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
rescue Exception
|
|
224
|
+
Log::warn_exception(self)
|
|
225
|
+
raise
|
|
226
|
+
# ensure
|
|
227
|
+
# Log::debug self, "CREATE_NODES: #{self}.number_of_nodes=#{no}"
|
|
228
|
+
#add_node(nil)
|
|
229
|
+
#self.number_of_nodes = no
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def create_and_add_node(ntask, mapper, opts={})
|
|
234
|
+
node = create_node(ntask) {|node|
|
|
235
|
+
if opts[:init_njob]
|
|
236
|
+
opts[:init_njob].call(node)
|
|
237
|
+
end
|
|
238
|
+
mapper.bind_input(node)
|
|
239
|
+
}
|
|
240
|
+
node
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def create_node(ntask, *params, &block)
|
|
244
|
+
if params.empty?
|
|
245
|
+
params = njob_creation_params
|
|
246
|
+
end
|
|
247
|
+
njob = ntask.create_njob(node_class_name, self, @opts, *params)
|
|
248
|
+
block.call(njob)
|
|
249
|
+
add_node(njob)
|
|
250
|
+
njob
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def node_class_name
|
|
254
|
+
ERR::Raise ERR::INTERNAL::NoRegisterService, self.class
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def njob_creation_params
|
|
258
|
+
[]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def assgin_number_of_nodes?
|
|
262
|
+
@number_of_nodes
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# def next_filter(mapper)
|
|
267
|
+
# @nodes_mutex.synchronize do
|
|
268
|
+
# ret = nil
|
|
269
|
+
# while !ret
|
|
270
|
+
# while @nodes_for_next_filters.empty?
|
|
271
|
+
# @nodes_cv.wait(@nodes_mutex)
|
|
272
|
+
# end
|
|
273
|
+
# ret = @nodes_for_next_filters.shift
|
|
274
|
+
# Log::debug(self, "NEXT_FILTER: #{ret}")
|
|
275
|
+
# end
|
|
276
|
+
# ret = nil if ret == :NIL
|
|
277
|
+
# ret
|
|
278
|
+
# end
|
|
279
|
+
# end
|
|
280
|
+
|
|
281
|
+
# def next_filter(mapper)
|
|
282
|
+
# @nodes_mutex.synchronize do
|
|
283
|
+
# while @nodes_for_next_filters.empty?
|
|
284
|
+
# @nodes_cv.wait(@nodes_mutex)
|
|
285
|
+
# end
|
|
286
|
+
# @nodes_for_next_filters.shift
|
|
287
|
+
# end
|
|
288
|
+
# end
|
|
289
|
+
|
|
290
|
+
def each_assigned_filter(&block)
|
|
291
|
+
loop do
|
|
292
|
+
input_filter = nil
|
|
293
|
+
@nodes_mutex.synchronize do
|
|
294
|
+
while @nodes_for_next_filters.empty?
|
|
295
|
+
@nodes_cv.wait(@nodes_mutex)
|
|
296
|
+
end
|
|
297
|
+
input_filter = @nodes_for_next_filters.shift
|
|
298
|
+
return unless input_filter
|
|
299
|
+
end
|
|
300
|
+
block.call input_filter
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def start_export(njob)
|
|
305
|
+
export = njob.start_export
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def create_import(processor)
|
|
309
|
+
processor.create_import(@opts[:prequeuing_policy])
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def each_export_by(njob, mapper, &block)
|
|
313
|
+
# block.call njob.export, :foo=>:bar
|
|
314
|
+
block.call njob.export
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def bind_export(exp, imp)
|
|
318
|
+
imp.no_import = 1
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
#
|
|
322
|
+
# job control
|
|
323
|
+
#
|
|
324
|
+
def break_running(njob = nil)
|
|
325
|
+
break_create_node
|
|
326
|
+
|
|
327
|
+
each_node do |tasklet|
|
|
328
|
+
tasklet.break_running unless tasklet.equal?(njob)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def break_create_node
|
|
333
|
+
# 作成中のものは完全に作成させるため
|
|
334
|
+
@controller.create_processor_mutex.synchronize do
|
|
335
|
+
if @create_node_thread && @create_node_thread.alive?
|
|
336
|
+
@create_node_thread.raise BreakCreateNode
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def abort_create_node
|
|
342
|
+
Log::debug(self, "ABORT_CREATE_NODE: S")
|
|
343
|
+
@controller.create_processor_mutex.synchronize do
|
|
344
|
+
Log::debug(self, "ABORT_CREATE_NODE: 1")
|
|
345
|
+
if @create_node_thread && @create_node_thread.alive?
|
|
346
|
+
Log::debug(self, "ABORT_CREATE_NODE: 2 ")
|
|
347
|
+
@create_node_thread.raise AbortCreateNode
|
|
348
|
+
Log::debug(self, "ABORT_CREATE_NODE: 3")
|
|
349
|
+
end
|
|
350
|
+
Log::debug(self, "ABORT_CREATE_NODE: E")
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def update_status(node, st)
|
|
355
|
+
@nodes_status_mutex.synchronize do
|
|
356
|
+
@nodes_status[node] = st
|
|
357
|
+
@nodes_status_cv.broadcast
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def watch_status?
|
|
362
|
+
@@watch_status
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def start_watch_node_status
|
|
366
|
+
Thread.start do
|
|
367
|
+
|
|
368
|
+
all_finished = false
|
|
369
|
+
while !@number_of_nodes || !all_finished
|
|
370
|
+
@nodes_status_mutex.synchronize do
|
|
371
|
+
@nodes_status_cv.wait(@nodes_status_mutex)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
all_finished = @number_of_nodes
|
|
375
|
+
Log::info(self) do |sio|
|
|
376
|
+
sio.puts "Status Changed: BEGIN #{self}"
|
|
377
|
+
each_node(:exist_only) do |node|
|
|
378
|
+
st = @nodes_status[node]
|
|
379
|
+
sio.puts " node: #{node} status: #{st.id2name}" if st
|
|
380
|
+
all_finished &&= st==:ST_FINISH
|
|
381
|
+
end
|
|
382
|
+
sio.puts "Status Changed: END #{self}"
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
Log::info self, "Monitoring finish: ALL NJOB finished"
|
|
386
|
+
end
|
|
387
|
+
nil
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def handle_exception(exp)
|
|
391
|
+
@controller.handle_exception(exp)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
class Context
|
|
395
|
+
def initialize(bjob)
|
|
396
|
+
@Pool = bjob.instance_eval{pool_dict}
|
|
397
|
+
@JobPool = bjob.instance_eval{job_pool_dict}
|
|
398
|
+
@__context = context
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# def create_proc(source)
|
|
402
|
+
# eval("proc{#{source}}", binding)
|
|
403
|
+
# end
|
|
404
|
+
|
|
405
|
+
def context
|
|
406
|
+
__binding
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
class GlobalBreak<Exception;end
|
|
410
|
+
def global_break
|
|
411
|
+
Thread.current.raise GlobalBreak
|
|
412
|
+
end
|
|
413
|
+
alias gbreak global_break
|
|
414
|
+
|
|
415
|
+
alias __binding binding
|
|
416
|
+
def binding
|
|
417
|
+
@__context
|
|
418
|
+
end
|
|
419
|
+
alias bind binding
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|