droonga-engine 1.0.9 → 1.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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/benchmark/timer-watcher/benchmark.rb +44 -0
  4. data/bin/droonga-engine-absorb-data +246 -187
  5. data/bin/droonga-engine-catalog-generate +12 -12
  6. data/bin/droonga-engine-catalog-modify +4 -4
  7. data/bin/droonga-engine-join +352 -171
  8. data/bin/droonga-engine-set-role +54 -0
  9. data/bin/droonga-engine-unjoin +107 -112
  10. data/droonga-engine.gemspec +3 -3
  11. data/install.sh +55 -36
  12. data/install/centos/functions.sh +2 -2
  13. data/install/debian/functions.sh +2 -2
  14. data/lib/droonga/address.rb +26 -24
  15. data/lib/droonga/buffered_tcp_socket.rb +65 -10
  16. data/lib/droonga/catalog/base.rb +9 -6
  17. data/lib/droonga/catalog/dataset.rb +17 -41
  18. data/lib/droonga/catalog/fetcher.rb +64 -0
  19. data/lib/droonga/catalog/generator.rb +245 -0
  20. data/lib/droonga/catalog/loader.rb +66 -0
  21. data/lib/droonga/{catalog_modifier.rb → catalog/modifier.rb} +11 -18
  22. data/lib/droonga/catalog/replicas_volume.rb +123 -0
  23. data/lib/droonga/catalog/schema.rb +37 -37
  24. data/lib/droonga/catalog/single_volume.rb +11 -3
  25. data/lib/droonga/catalog/slice.rb +10 -6
  26. data/lib/droonga/catalog/{collection_volume.rb → slices_volume.rb} +47 -11
  27. data/lib/droonga/catalog/version1.rb +47 -19
  28. data/lib/droonga/catalog/version2.rb +11 -10
  29. data/lib/droonga/catalog/version2_validator.rb +4 -4
  30. data/lib/droonga/catalog/volume.rb +17 -5
  31. data/lib/droonga/changable.rb +25 -0
  32. data/lib/droonga/cluster.rb +237 -0
  33. data/lib/droonga/collector_runner.rb +4 -0
  34. data/lib/droonga/collectors.rb +2 -1
  35. data/lib/droonga/collectors/recursive_sum.rb +26 -0
  36. data/lib/droonga/command/droonga_engine.rb +404 -127
  37. data/lib/droonga/command/droonga_engine_service.rb +47 -11
  38. data/lib/droonga/command/droonga_engine_worker.rb +21 -1
  39. data/lib/droonga/command/remote_command_base.rb +78 -0
  40. data/lib/droonga/command/serf_event_handler.rb +29 -20
  41. data/lib/droonga/data_absorber_client.rb +222 -0
  42. data/lib/droonga/database_scanner.rb +106 -0
  43. data/lib/droonga/{live_nodes_list_loader.rb → deferrable.rb} +11 -24
  44. data/lib/droonga/differ.rb +58 -0
  45. data/lib/droonga/dispatcher.rb +155 -32
  46. data/lib/droonga/distributed_command_planner.rb +9 -11
  47. data/lib/droonga/engine.rb +83 -78
  48. data/lib/droonga/engine/version.rb +1 -1
  49. data/lib/droonga/engine_node.rb +301 -0
  50. data/lib/droonga/engine_state.rb +62 -40
  51. data/lib/droonga/farm.rb +44 -5
  52. data/lib/droonga/file_observer.rb +16 -12
  53. data/lib/droonga/fluent_message_receiver.rb +98 -29
  54. data/lib/droonga/fluent_message_sender.rb +30 -23
  55. data/lib/droonga/forward_buffer.rb +160 -0
  56. data/lib/droonga/forwarder.rb +73 -40
  57. data/lib/droonga/handler.rb +7 -6
  58. data/lib/droonga/handler_messenger.rb +15 -6
  59. data/lib/droonga/handler_runner.rb +6 -1
  60. data/lib/droonga/internal_fluent_message_receiver.rb +28 -8
  61. data/lib/droonga/job_pusher.rb +10 -7
  62. data/lib/droonga/job_receiver.rb +6 -4
  63. data/lib/droonga/logger.rb +7 -1
  64. data/lib/droonga/node_name.rb +90 -0
  65. data/lib/droonga/node_role.rb +72 -0
  66. data/lib/droonga/path.rb +34 -9
  67. data/lib/droonga/planner.rb +73 -7
  68. data/lib/droonga/plugin/async_command.rb +154 -0
  69. data/lib/droonga/plugins/catalog.rb +1 -0
  70. data/lib/droonga/plugins/crud.rb +22 -6
  71. data/lib/droonga/plugins/dump.rb +66 -135
  72. data/lib/droonga/plugins/groonga/delete.rb +13 -0
  73. data/lib/droonga/plugins/search/distributed_search_planner.rb +4 -4
  74. data/lib/droonga/plugins/system.rb +5 -26
  75. data/lib/droonga/plugins/system/absorb_data.rb +405 -0
  76. data/lib/droonga/plugins/system/statistics.rb +71 -0
  77. data/lib/droonga/plugins/system/status.rb +53 -0
  78. data/lib/droonga/process_control_protocol.rb +3 -1
  79. data/lib/droonga/process_supervisor.rb +32 -15
  80. data/lib/droonga/reducer.rb +69 -0
  81. data/lib/droonga/safe_file_writer.rb +1 -1
  82. data/lib/droonga/serf.rb +207 -276
  83. data/lib/droonga/serf/agent.rb +228 -0
  84. data/lib/droonga/serf/command.rb +94 -0
  85. data/lib/droonga/serf/downloader.rb +120 -0
  86. data/lib/droonga/serf/remote_command.rb +348 -0
  87. data/lib/droonga/serf/tag.rb +56 -0
  88. data/lib/droonga/service_installation.rb +2 -2
  89. data/lib/droonga/session.rb +49 -1
  90. data/lib/droonga/single_step.rb +6 -11
  91. data/lib/droonga/single_step_definition.rb +32 -1
  92. data/lib/droonga/slice.rb +14 -9
  93. data/lib/droonga/supervisor.rb +27 -20
  94. data/lib/droonga/test/stub_handler_messenger.rb +2 -1
  95. data/lib/droonga/timestamp.rb +69 -0
  96. data/lib/droonga/worker_process_agent.rb +33 -15
  97. data/sample/cluster-state.json +8 -0
  98. data/sample/cluster/Rakefile +30 -6
  99. data/test/command/fixture/integer-key-table.jsons +11 -0
  100. data/test/command/fixture/string-key-table.jsons +11 -0
  101. data/test/command/run-test.rb +4 -0
  102. data/test/command/suite/add/error/invalid-integer.expected +3 -3
  103. data/test/command/suite/add/error/invalid-time.expected +3 -3
  104. data/test/command/suite/add/{minimum.expected → key-integer.expected} +0 -0
  105. data/test/command/suite/add/{minimum.test → key-integer.test} +0 -0
  106. data/test/command/suite/add/key-string.expected +6 -0
  107. data/test/command/suite/add/key-string.test +9 -0
  108. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.expected +6 -0
  109. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.test +9 -0
  110. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.expected +6 -0
  111. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.test +9 -0
  112. data/test/command/suite/add/without-values.expected +6 -0
  113. data/test/command/suite/add/without-values.test +11 -0
  114. data/test/command/suite/dump/column/index.expected +33 -1
  115. data/test/command/suite/dump/column/index.test +1 -0
  116. data/test/command/suite/dump/column/scalar.expected +29 -1
  117. data/test/command/suite/dump/column/scalar.test +1 -0
  118. data/test/command/suite/dump/column/vector.expected +29 -1
  119. data/test/command/suite/dump/column/vector.test +1 -0
  120. data/test/command/suite/dump/record/scalar.catalog.json +12 -0
  121. data/test/command/suite/dump/record/scalar.expected +84 -0
  122. data/test/command/suite/dump/record/scalar.test +16 -0
  123. data/test/command/suite/dump/record/vector/reference.expected +83 -1
  124. data/test/command/suite/dump/record/vector/reference.test +1 -0
  125. data/test/command/suite/dump/table/array.expected +27 -1
  126. data/test/command/suite/dump/table/array.test +1 -0
  127. data/test/command/suite/dump/table/double_array_trie.expected +27 -1
  128. data/test/command/suite/dump/table/double_array_trie.test +1 -0
  129. data/test/command/suite/dump/table/hash.expected +27 -1
  130. data/test/command/suite/dump/table/hash.test +1 -0
  131. data/test/command/suite/dump/table/patricia_trie.expected +27 -1
  132. data/test/command/suite/dump/table/patricia_trie.test +1 -0
  133. data/test/command/suite/groonga/delete/{success.expected → key-integer.expected} +0 -0
  134. data/test/command/suite/groonga/delete/key-integer.test +17 -0
  135. data/test/command/suite/groonga/delete/key-string.expected +19 -0
  136. data/test/command/suite/groonga/delete/{success.test → key-string.test} +4 -6
  137. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.expected +19 -0
  138. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.test +17 -0
  139. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.expected +19 -0
  140. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.test +17 -0
  141. data/test/command/suite/message/error/missing-dataset.test +1 -0
  142. data/test/command/suite/system/absorb-data/records.catalog.json +58 -0
  143. data/test/command/suite/system/absorb-data/records.expected +32 -0
  144. data/test/command/suite/system/absorb-data/records.test +24 -0
  145. data/test/command/suite/system/statistics/object/count/empty.expected +11 -0
  146. data/test/command/suite/system/statistics/object/count/empty.test +12 -0
  147. data/test/command/suite/system/statistics/object/count/per-volume/empty.catalog.json +36 -0
  148. data/test/command/suite/system/statistics/object/count/per-volume/empty.expected +19 -0
  149. data/test/command/suite/system/statistics/object/count/per-volume/empty.test +12 -0
  150. data/test/command/suite/system/statistics/object/count/per-volume/record.catalog.json +40 -0
  151. data/test/command/suite/system/statistics/object/count/per-volume/record.expected +19 -0
  152. data/test/command/suite/system/statistics/object/count/per-volume/record.test +23 -0
  153. data/test/command/suite/system/statistics/object/count/per-volume/schema.catalog.json +40 -0
  154. data/test/command/suite/system/statistics/object/count/per-volume/schema.expected +19 -0
  155. data/test/command/suite/system/statistics/object/count/per-volume/schema.test +13 -0
  156. data/test/command/suite/system/statistics/object/count/record.catalog.json +12 -0
  157. data/test/command/suite/system/statistics/object/count/record.expected +11 -0
  158. data/test/command/suite/system/statistics/object/count/record.test +23 -0
  159. data/test/command/suite/system/statistics/object/count/schema.catalog.json +12 -0
  160. data/test/command/suite/system/statistics/object/count/schema.expected +11 -0
  161. data/test/command/suite/system/statistics/object/count/schema.test +13 -0
  162. data/test/command/suite/system/status.expected +3 -2
  163. data/test/unit/catalog/test_dataset.rb +4 -1
  164. data/test/unit/{test_catalog_generator.rb → catalog/test_generator.rb} +2 -2
  165. data/test/unit/catalog/test_replicas_volume.rb +79 -0
  166. data/test/unit/catalog/test_single_volume.rb +2 -2
  167. data/test/unit/catalog/test_slice.rb +33 -1
  168. data/test/unit/catalog/{test_collection_volume.rb → test_slices_volume.rb} +72 -11
  169. data/test/unit/catalog/test_version2.rb +3 -0
  170. data/test/unit/helper/distributed_search_planner_helper.rb +2 -2
  171. data/test/unit/plugins/catalog/test_fetch.rb +4 -4
  172. data/test/unit/plugins/crud/test_add.rb +44 -4
  173. data/test/unit/plugins/groonga/test_column_create.rb +4 -4
  174. data/test/unit/plugins/groonga/test_column_list.rb +4 -4
  175. data/test/unit/plugins/groonga/test_column_remove.rb +4 -4
  176. data/test/unit/plugins/groonga/test_column_rename.rb +4 -4
  177. data/test/unit/plugins/groonga/test_delete.rb +73 -10
  178. data/test/unit/plugins/groonga/test_table_create.rb +4 -4
  179. data/test/unit/plugins/groonga/test_table_list.rb +4 -4
  180. data/test/unit/plugins/groonga/test_table_remove.rb +4 -4
  181. data/test/unit/plugins/search/test_handler.rb +4 -4
  182. data/test/unit/plugins/search/test_planner.rb +4 -2
  183. data/test/unit/plugins/system/test_status.rb +31 -15
  184. data/test/unit/plugins/test_watch.rb +16 -16
  185. data/test/unit/test_address.rb +4 -4
  186. metadata +134 -35
  187. data/lib/droonga/catalog/volume_collection.rb +0 -79
  188. data/lib/droonga/catalog_fetcher.rb +0 -53
  189. data/lib/droonga/catalog_generator.rb +0 -243
  190. data/lib/droonga/catalog_loader.rb +0 -56
  191. data/lib/droonga/command/remote.rb +0 -404
  192. data/lib/droonga/data_absorber.rb +0 -264
  193. data/lib/droonga/node_status.rb +0 -71
  194. data/lib/droonga/serf_downloader.rb +0 -115
  195. data/test/unit/catalog/test_volume_collection.rb +0 -78
@@ -24,7 +24,7 @@ require "droonga/catalog/version2_validator"
24
24
  module Droonga
25
25
  module Catalog
26
26
  class Version2 < Base
27
- def initialize(data, path)
27
+ def initialize(raw, path)
28
28
  super
29
29
  validate
30
30
  prepare_data
@@ -44,16 +44,17 @@ module Droonga
44
44
  volume.slices.each do |slice|
45
45
  volume_address = slice.volume.address
46
46
  if volume_address.node == node
47
- name = volume_address.name
47
+ local_name = volume_address.local_name
48
48
  path = Path.databases(base_path) +
49
- device + name + "db"
50
- migrate_database_location(path, device, name)
49
+ device + local_name + "db"
50
+ migrate_database_location(path, device, local_name)
51
51
 
52
52
  options = {
53
- :dataset => dataset_name,
54
- :database => path.to_s,
53
+ :label => volume_address.to_s,
54
+ :dataset => dataset_name,
55
+ :database => path.to_s,
55
56
  :n_workers => n_workers,
56
- :plugins => plugins
57
+ :plugins => plugins
57
58
  }
58
59
  results[volume_address.to_s] = options
59
60
  end
@@ -69,13 +70,13 @@ module Droonga
69
70
 
70
71
  private
71
72
  def validate
72
- validator = Version2Validator.new(@data, @path)
73
+ validator = Version2Validator.new(@raw, @path)
73
74
  validator.validate
74
75
  end
75
76
 
76
77
  def prepare_data
77
78
  @datasets = {}
78
- @data["datasets"].each do |name, dataset|
79
+ @raw["datasets"].each do |name, dataset|
79
80
  @datasets[name] = Dataset.new(name, dataset)
80
81
  end
81
82
  end
@@ -83,7 +84,7 @@ module Droonga
83
84
  def collect_all_nodes
84
85
  nodes = []
85
86
  @datasets.each do |name, dataset|
86
- nodes += dataset.all_nodes
87
+ nodes.concat(dataset.all_nodes)
87
88
  end
88
89
  nodes.sort.uniq
89
90
  end
@@ -18,8 +18,8 @@ require "droonga/catalog/errors"
18
18
  module Droonga
19
19
  module Catalog
20
20
  class Version2Validator
21
- def initialize(data, path)
22
- @data = data
21
+ def initialize(raw, path)
22
+ @raw = raw
23
23
  @path = path
24
24
  end
25
25
 
@@ -35,11 +35,11 @@ module Droonga
35
35
 
36
36
  private
37
37
  def validate_datasets
38
- unless @data.key?("datasets")
38
+ unless @raw.key?("datasets")
39
39
  required_parameter_is_missing("datasets")
40
40
  return
41
41
  end
42
- @data["datasets"].each do |name, dataset|
42
+ @raw["datasets"].each do |name, dataset|
43
43
  validate_dataset(name, dataset)
44
44
  end
45
45
  end
@@ -14,17 +14,29 @@
14
14
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
15
 
16
16
  require "droonga/catalog/single_volume"
17
- require "droonga/catalog/collection_volume"
17
+ require "droonga/catalog/slices_volume"
18
+ require "droonga/catalog/replicas_volume"
18
19
 
19
20
  module Droonga
20
21
  module Catalog
21
22
  module Volume
23
+ class UnknownTypeVolume < ArgumentError
24
+ def initialize(raw)
25
+ super("volume must have one of 'address', 'slices' or 'replicas': " +
26
+ "#{raw.inspect}")
27
+ end
28
+ end
29
+
22
30
  class << self
23
- def create(dataset, raw_volume)
24
- if raw_volume.key?("address")
25
- SingleVolume.new(raw_volume)
31
+ def create(dataset, raw)
32
+ if raw.key?("address")
33
+ SingleVolume.new(raw)
34
+ elsif raw.key?("slices")
35
+ SlicesVolume.new(dataset, raw)
36
+ elsif raw.key?("replicas")
37
+ ReplicasVolume.new(dataset, raw)
26
38
  else
27
- CollectionVolume.new(dataset, raw_volume)
39
+ raise UnknownTypeVolume.new(raw)
28
40
  end
29
41
  end
30
42
  end
@@ -0,0 +1,25 @@
1
+ # Copyright (C) 2015 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ module Droonga
17
+ module Changable
18
+ attr_writer :on_change
19
+
20
+ private
21
+ def on_change
22
+ @on_change.call if @on_change
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,237 @@
1
+ # Copyright (C) 2014-2015 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "droonga/loggable"
17
+ require "droonga/changable"
18
+ require "droonga/path"
19
+ require "droonga/file_observer"
20
+ require "droonga/engine_node"
21
+ require "droonga/differ"
22
+
23
+ module Droonga
24
+ class Cluster
25
+ include Loggable
26
+ include Changable
27
+
28
+ class NoCatalogLoaded < StandardError
29
+ end
30
+
31
+ class NotStartedYet < StandardError
32
+ end
33
+
34
+ class UnknownTarget < StandardError
35
+ end
36
+
37
+ class NoAcceptableReceiver < StandardError
38
+ def initialize(message)
39
+ super(message.inspect)
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def load_state_file
45
+ path = Path.cluster_state
46
+
47
+ return default_state unless path.exist?
48
+
49
+ contents = path.read
50
+ return default_state if contents.empty?
51
+
52
+ begin
53
+ JSON.parse(contents)
54
+ rescue JSON::ParserError
55
+ default_state
56
+ end
57
+ end
58
+
59
+ def default_state
60
+ {}
61
+ end
62
+ end
63
+
64
+ attr_accessor :catalog
65
+
66
+ def initialize(loop, params)
67
+ @loop = loop
68
+
69
+ @params = params
70
+ @catalog = params[:catalog]
71
+ @state = nil
72
+
73
+ reload
74
+ end
75
+
76
+ def start_observe
77
+ return if @file_observer
78
+ logger.trace("start_observe: start")
79
+ @file_observer = FileObserver.new(@loop, Path.cluster_state)
80
+ @file_observer.on_change = lambda do
81
+ reload
82
+ end
83
+ @file_observer.start
84
+ logger.trace("start_observe: done")
85
+ end
86
+
87
+ def stop_observe
88
+ return unless @file_observer
89
+ logger.trace("stop_observe: start")
90
+ @file_observer.stop
91
+ @file_observer = nil
92
+ logger.trace("stop_observe: done")
93
+ end
94
+
95
+ def start
96
+ logger.trace("start: start")
97
+ engine_nodes.each(&:start)
98
+ start_observe
99
+ logger.trace("start: done")
100
+ end
101
+
102
+ def shutdown
103
+ logger.trace("shutdown: start")
104
+ stop_observe
105
+ engine_nodes.each(&:shutdown)
106
+ logger.trace("shutdown: done")
107
+ end
108
+
109
+ def refresh_connection_for(name)
110
+ logger.trace("refresh_connection_for(#{name}): start")
111
+ engine_nodes.each do |node|
112
+ if node.name == name
113
+ node.refresh_connection
114
+ end
115
+ end
116
+ logger.trace("refresh_connection_for(#{name}): done")
117
+ end
118
+
119
+ def reload
120
+ logger.trace("reload: start")
121
+ if @state
122
+ old_state = @state.dup
123
+ else
124
+ old_state = nil
125
+ end
126
+ @state = self.class.load_state_file
127
+ if @state == old_state
128
+ logger.info("cluster state not changed")
129
+ else
130
+ logger.info("cluster state changed",
131
+ :before => old_state,
132
+ :after => @state,
133
+ :diff => Differ.diff(old_state, @state))
134
+ clear_cache
135
+ engine_nodes.each(&:resume)
136
+ on_change
137
+ end
138
+ logger.trace("reload: done")
139
+ end
140
+
141
+ def engine_nodes
142
+ @engine_nodes ||= create_engine_nodes
143
+ end
144
+
145
+ def engine_nodes_status
146
+ nodes_status = {}
147
+ engine_nodes.each do |node|
148
+ nodes_status[node.name] = {
149
+ "status" => node.status,
150
+ }
151
+ end
152
+ sorted_nodes_status = {}
153
+ nodes_status.keys.sort.each do |key|
154
+ sorted_nodes_status[key] = nodes_status[key]
155
+ end
156
+ sorted_nodes_status
157
+ end
158
+
159
+ def forward(message, destination)
160
+ receiver = destination["to"]
161
+ receiver_node_name = receiver.match(/\A[^:]+:\d+\/[^.]+/).to_s
162
+ raise NotStartedYet.new unless @engine_nodes
163
+ @engine_nodes.each do |node|
164
+ if node.name == receiver_node_name
165
+ node.forward(message, destination)
166
+ return
167
+ end
168
+ end
169
+ raise UnknownTarget.new(receiver)
170
+ end
171
+
172
+ def bounce(message)
173
+ role = message["targetRole"].downcase
174
+ logger.info("bounce: trying to bounce message to another " +
175
+ "node with the role: #{role}")
176
+ raise NotStartedYet.new unless @engine_nodes
177
+
178
+ acceptable_nodes = engine_nodes.select do |node|
179
+ node.role == role and
180
+ node.live?
181
+ end
182
+ receiver = acceptable_nodes.sample
183
+ if receiver
184
+ receiver.bounce(message)
185
+ else
186
+ logger.error("bounce: no available node with the role #{role}",
187
+ :message => message)
188
+ # raise NoAcceptableReceiver.new(message)
189
+ end
190
+ end
191
+
192
+ def engine_node_names
193
+ @engine_node_names ||= engine_nodes.collect(&:name)
194
+ end
195
+
196
+ def readable_nodes
197
+ @readable_nodes ||= engine_nodes.select do |node|
198
+ node.readable?
199
+ end.collect(&:name)
200
+ end
201
+
202
+ def writable_nodes
203
+ @writable_nodes ||= engine_nodes.select do |node|
204
+ node.writable?
205
+ end.collect(&:name)
206
+ end
207
+
208
+ private
209
+ def clear_cache
210
+ @engine_nodes.each(&:shutdown) if @engine_nodes
211
+ @engine_nodes = nil
212
+ @engine_node_names = nil
213
+ @readable_nodes = nil
214
+ @writable_nodes = nil
215
+ end
216
+
217
+ def all_node_names
218
+ raise NoCatalogLoaded.new unless @catalog
219
+ @catalog.all_nodes
220
+ end
221
+
222
+ def create_engine_nodes
223
+ all_node_names.collect do |name|
224
+ node_state = @state[name] || {}
225
+ EngineNode.new(@loop,
226
+ name,
227
+ node_state,
228
+ :auto_close_timeout =>
229
+ @params[:internal_connection_lifetime])
230
+ end
231
+ end
232
+
233
+ def log_tag
234
+ "cluster_state"
235
+ end
236
+ end
237
+ end
@@ -44,6 +44,10 @@ module Droonga
44
44
  logger.trace("collector: done")
45
45
  end
46
46
 
47
+ def collectable?(message)
48
+ not find_collector_class(message).nil?
49
+ end
50
+
47
51
  private
48
52
  def find_collector_class(message)
49
53
  @collector_classes.find do |collector_class|
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014 Droonga Project
1
+ # Copyright (C) 2014-2015 Droonga Project
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -16,3 +16,4 @@
16
16
  require "droonga/collectors/and"
17
17
  require "droonga/collectors/or"
18
18
  require "droonga/collectors/sum"
19
+ require "droonga/collectors/recursive_sum"
@@ -0,0 +1,26 @@
1
+ # Copyright (C) 2015 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ module Droonga
17
+ module Collectors
18
+ class RecursiveSum
19
+ class << self
20
+ def operator
21
+ "recursive-sum"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014 Droonga Project
1
+ # Copyright (C) 2014-2015 Droonga Project
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -23,12 +23,16 @@ require "coolio"
23
23
  require "sigdump/setup"
24
24
 
25
25
  require "droonga/engine/version"
26
+ require "droonga/loggable"
27
+ require "droonga/deferrable"
26
28
  require "droonga/path"
27
- require "droonga/address"
29
+ require "droonga/node_name"
30
+ require "droonga/forwarder"
28
31
  require "droonga/serf"
29
- require "droonga/node_status"
32
+ require "droonga/cluster"
30
33
  require "droonga/file_observer"
31
34
  require "droonga/process_supervisor"
35
+ require "droonga/differ"
32
36
 
33
37
  module Droonga
34
38
  module Command
@@ -48,16 +52,13 @@ module Droonga
48
52
  parse_command_line_arguments!(command_line_arguments)
49
53
 
50
54
  setup_path
55
+ setup_log
51
56
 
52
57
  if @configuration.daemon?
53
58
  Process.daemon
54
59
  end
55
60
 
56
- open_log_file do
57
- write_pid_file do
58
- run_main_loop
59
- end
60
- end
61
+ run_main_loop
61
62
  end
62
63
 
63
64
  private
@@ -80,104 +81,111 @@ module Droonga
80
81
  end
81
82
  end
82
83
 
84
+ def setup_log
85
+ ENV["DROONGA_LOG_LEVEL"] = @configuration.log_level
86
+ end
87
+
83
88
  def run_main_loop
84
89
  main_loop = MainLoop.new(@configuration)
85
90
  main_loop.run
86
91
  end
87
92
 
88
- def open_log_file
89
- if @configuration.log_file
90
- File.open(@configuration.log_file, "a") do |file|
91
- $stdout.reopen(file)
92
- $stderr.reopen(file)
93
- yield
94
- end
95
- else
96
- yield
97
- end
98
- end
99
-
100
- def write_pid_file
101
- if @configuration.pid_file_path
102
- @configuration.pid_file_path.open("w") do |file|
103
- file.puts(Process.pid)
104
- end
105
- begin
106
- yield
107
- ensure
108
- FileUtils.rm_f(@configuration.pid_file_path.to_s)
109
- end
110
- else
111
- yield
112
- end
113
- end
114
-
115
93
  class Configuration
116
- attr_reader :host, :port, :tag, :log_file, :pid_file_path
117
94
  attr_reader :ready_notify_fd
118
95
  def initialize
119
- config = load_config
96
+ @config = nil
97
+
98
+ @host = nil
99
+ @port = nil
100
+ @tag = nil
120
101
 
121
- @host = config["host"] || Address::DEFAULT_HOST
122
- @port = config["port"] || Address::DEFAULT_PORT
123
- @tag = config["tag"] || Address::DEFAULT_TAG
102
+ @internal_connection_lifetime = nil
124
103
 
104
+ @log_level = nil
125
105
  @log_file = nil
126
- @daemon = false
106
+ @daemon = nil
127
107
  @pid_file_path = nil
128
108
  @ready_notify_fd = nil
129
109
 
130
- if have_config_file?
131
- self.pid_file_path = config["pid_file"] if config["pid_file"]
132
- self.log_file = config["log_file"] || Path.default_log_file
133
- self.log_level = config["log_level"] if config.include?("log_level")
134
- end
110
+ @listen_fd = nil
111
+ @heartbeat_fd = nil
112
+ @serf_agent_pid = nil
113
+ end
114
+
115
+ def engine_name
116
+ "#{host}:#{port}/#{tag}"
135
117
  end
136
118
 
137
- def have_config_file?
138
- File.exist?(Path.config)
119
+ def address_family
120
+ ip_address = IPAddr.new(IPSocket.getaddress(host))
121
+ ip_address.family
139
122
  end
140
123
 
141
- def load_config
142
- if have_config_file?
143
- YAML.load_file(Path.config)
144
- else
145
- {}
146
- end
124
+ def host
125
+ @host || config["host"] || default_host
147
126
  end
148
127
 
149
- def engine_name
150
- "#{@host}:#{@port}/#{@tag}"
128
+ def port
129
+ @port || config["port"] || default_port
151
130
  end
152
131
 
153
- def address_family
154
- ip_address = IPAddr.new(IPSocket.getaddress(@host))
155
- ip_address.family
132
+ def tag
133
+ @tag || config["tag"] || default_tag
156
134
  end
157
135
 
158
- def log_level
159
- ENV["DROONGA_LOG_LEVEL"] || Logger::Level.default
136
+ def internal_connection_lifetime
137
+ @internal_connection_lifetime ||
138
+ config["internal_connection_lifetime"] ||
139
+ default_internal_connection_lifetime
160
140
  end
161
141
 
162
- def log_level=(level)
163
- ENV["DROONGA_LOG_LEVEL"] = level
142
+ def log_level
143
+ @log_level || config["log_level"] || default_log_level
164
144
  end
165
145
 
166
- def log_file=(file)
167
- @log_file = File.expand_path(file)
146
+ def log_file_path
147
+ @log_file_path || config["log_file"] || default_log_file_path
168
148
  end
169
149
 
170
- def pid_file_path=(path)
171
- @pid_file_path = Pathname.new(path).expand_path
150
+ def pid_file_path
151
+ @pid_file_path || config["pid_file"] || default_pid_file_path
172
152
  end
173
153
 
174
154
  def daemon?
175
- @daemon
155
+ daemon = @daemon
156
+ daemon = config["daemon"] if daemon.nil?
157
+ daemon = false if daemon.nil?
158
+ daemon
176
159
  end
177
160
 
178
- def to_command_line
161
+ def to_engine_command_line
162
+ command_line_options = [
163
+ "--host", host,
164
+ "--port", port.to_s,
165
+ "--tag", tag,
166
+ "--internal-connection-lifetime",
167
+ internal_connection_lifetime.to_s,
168
+ "--log-level", log_level,
169
+ ]
170
+ if log_file_path
171
+ command_line_options.concat(["--log-file", log_file_path.to_s])
172
+ end
173
+ if pid_file_path
174
+ command_line_options.concat(["--pid-file", pid_file_path.to_s])
175
+ end
176
+ if daemon?
177
+ command_line_options << "--daemon"
178
+ else
179
+ command_line_options << "--no-daemon"
180
+ end
181
+ command_line_options
182
+ end
183
+
184
+ def to_service_command_line
179
185
  command_line_options = [
180
186
  "--engine-name", engine_name,
187
+ "--internal-connection-lifetime",
188
+ internal_connection_lifetime.to_s,
181
189
  ]
182
190
  command_line_options
183
191
  end
@@ -188,35 +196,104 @@ module Droonga
188
196
  add_process_options(parser)
189
197
  add_path_options(parser)
190
198
  add_notification_options(parser)
199
+ add_internal_options(parser)
191
200
  end
192
201
 
193
202
  def listen_socket
194
- @listen_socket ||= TCPServer.new(@host, @port)
203
+ @listen_socket ||= create_listen_socket
195
204
  end
196
205
 
197
206
  def heartbeat_socket
198
- @heartbeat_socket ||= bind_heartbeat_socket
207
+ @heartbeat_socket ||= create_heartbeat_socket
208
+ end
209
+
210
+ def serf_agent_pid
211
+ @serf_agent_pid
199
212
  end
200
213
 
201
214
  private
215
+ def default_host
216
+ NodeName::DEFAULT_HOST
217
+ end
218
+
219
+ def default_port
220
+ NodeName::DEFAULT_PORT
221
+ end
222
+
223
+ def default_tag
224
+ NodeName::DEFAULT_TAG
225
+ end
226
+
227
+ def default_internal_connection_lifetime
228
+ Forwarder::DEFAULT_AUTO_CLOSE_TIMEOUT_SECONDS
229
+ end
230
+
231
+ def default_log_level
232
+ ENV["DROONGA_LOG_LEVEL"] || Logger::Level.default
233
+ end
234
+
235
+ def default_log_file_path
236
+ nil
237
+ end
238
+
239
+ def default_pid_file_path
240
+ nil
241
+ end
242
+
243
+ def normalize_path(path)
244
+ if path == "-"
245
+ nil
246
+ else
247
+ Pathname.new(path).expand_path
248
+ end
249
+ end
250
+
251
+ def config
252
+ @config ||= load_config
253
+ end
254
+
255
+ def load_config
256
+ config_path = Path.config
257
+ return {} unless config_path.exist?
258
+
259
+ config = YAML.load_file(config_path)
260
+ path_keys = ["log_file", "pid_file"]
261
+ path_keys.each do |path_key|
262
+ path = config[path_key]
263
+ next if path.nil?
264
+
265
+ path = Pathname.new(path)
266
+ unless path.absolute?
267
+ path = (config_path.dirname + path).expand_path
268
+ end
269
+ config[path_key] = path
270
+ end
271
+ config
272
+ end
273
+
202
274
  def add_connection_options(parser)
203
275
  parser.separator("")
204
276
  parser.separator("Connection:")
205
277
  parser.on("--host=HOST",
206
278
  "The host name of the Droonga engine",
207
- "(#{@host})") do |host|
279
+ "(#{default_host})") do |host|
208
280
  @host = host
209
281
  end
210
282
  parser.on("--port=PORT", Integer,
211
283
  "The port number of the Droonga engine",
212
- "(#{@port})") do |port|
284
+ "(#{default_port})") do |port|
213
285
  @port = port
214
286
  end
215
287
  parser.on("--tag=TAG",
216
288
  "The tag of the Droonga engine",
217
- "(#{@tag})") do |tag|
289
+ "(#{default_tag})") do |tag|
218
290
  @tag = tag
219
291
  end
292
+ parser.on("--internal-connection-lifetime=SECONDS", Float,
293
+ "The time to expire internal connections, in seconds",
294
+ "(#{default_internal_connection_lifetime})") do |seconds|
295
+ @internal_connection_lifetime = seconds
296
+ end
220
297
  end
221
298
 
222
299
  def add_log_options(parser)
@@ -227,12 +304,13 @@ module Droonga
227
304
  parser.on("--log-level=LEVEL", levels,
228
305
  "The log level of the Droonga engine",
229
306
  "[#{levels_label}]",
230
- "(#{log_level})") do |level|
231
- self.log_level = level
307
+ "(#{default_log_level})") do |level|
308
+ @log_level = level
232
309
  end
233
310
  parser.on("--log-file=FILE",
234
- "Output logs to FILE") do |file|
235
- self.log_file = file
311
+ "Output logs to FILE",
312
+ "(#{default_log_file_path})") do |path|
313
+ @log_file_path = normalize_path(path)
236
314
  end
237
315
  end
238
316
 
@@ -249,7 +327,7 @@ module Droonga
249
327
  end
250
328
  parser.on("--pid-file=PATH",
251
329
  "Put PID to PATH") do |path|
252
- self.pid_file_path = path
330
+ @pid_file_path = normalize_path(path)
253
331
  end
254
332
  end
255
333
 
@@ -260,6 +338,7 @@ module Droonga
260
338
  "Use DIR as the base directory",
261
339
  "(#{Path.base})") do |dir|
262
340
  Path.base = File.expand_path(dir)
341
+ @config = nil
263
342
  end
264
343
  end
265
344
 
@@ -272,34 +351,112 @@ module Droonga
272
351
  end
273
352
  end
274
353
 
275
- def bind_heartbeat_socket
276
- socket = UDPSocket.new(address_family)
277
- socket.bind(@host, @port)
278
- socket
354
+ def add_internal_options(parser)
355
+ parser.separator("")
356
+ parser.separator("Internal:")
357
+ parser.on("--listen-fd=FD", Integer,
358
+ "FD of listen socket") do |fd|
359
+ @listen_fd = fd
360
+ end
361
+ parser.on("--heartbeat-fd=FD", Integer,
362
+ "FD of heartbeat socket") do |fd|
363
+ @heartbeat_fd = fd
364
+ end
365
+ parser.on("--serf-agent-pid=PID", Integer,
366
+ "PID of Serf agent") do |pid|
367
+ @serf_agent_pid = pid
368
+ end
369
+ end
370
+
371
+ def create_listen_socket
372
+ begin
373
+ TCPServer.new(host, port)
374
+ rescue Errno::EADDRINUSE
375
+ raise if @listen_fd.nil?
376
+ TCPServer.for_fd(@listen_fd)
377
+ end
378
+ end
379
+
380
+ def create_heartbeat_socket
381
+ begin
382
+ socket = UDPSocket.new(address_family)
383
+ socket.bind(host, port)
384
+ socket
385
+ rescue Errno::EADDRINUSE
386
+ raise if @heartbeat_fd.nil?
387
+ UDPSocket.for_fd(@heartbeat_fd)
388
+ end
279
389
  end
280
390
  end
281
391
 
282
392
  class MainLoop
393
+ include Loggable
394
+
283
395
  def initialize(configuration)
284
396
  @configuration = configuration
397
+ ENV["DROONGA_ENGINE_NAME"] = @configuration.engine_name
285
398
  @loop = Coolio::Loop.default
399
+ @log_file = nil
400
+ @pid_file_path = nil
286
401
  end
287
402
 
288
403
  def run
289
- @serf = run_serf
290
- @service_runner = run_service
291
- setup_initial_on_ready
292
- @catalog_observer = run_catalog_observer
293
- @command_runner = run_command_runner
404
+ reopen_log_file
405
+ write_pid_file do
406
+ run_internal
407
+ end
408
+ end
409
+
410
+ private
411
+ def reopen_log_file
412
+ return if @configuration.log_file_path.nil?
413
+ @log_file = @configuration.log_file_path.open("a")
414
+ $stdout.reopen(@log_file)
415
+ $stderr.reopen(@log_file)
416
+ end
417
+
418
+ def write_pid_file
419
+ @pid_file_path = @configuration.pid_file_path
420
+ if @pid_file_path
421
+ @pid_file_path.open("w") do |file|
422
+ file.puts(Process.pid)
423
+ end
424
+ begin
425
+ yield
426
+ ensure
427
+ FileUtils.rm_f(@pid_file_path.to_s)
428
+ end
429
+ else
430
+ yield
431
+ end
432
+ end
433
+
434
+ def run_internal
435
+ logger.trace("run_internal: start")
436
+ start_serf
437
+ @serf_agent.on_ready = lambda do
438
+ logger.trace("run_internal: serf agent is ready")
439
+ @serf.initialize_tags
440
+ @serf.update_cluster_state
441
+ @service_runner = run_service
442
+ setup_initial_on_ready
443
+ @catalog_observer = run_catalog_observer
444
+ @cluster_state_observer = run_cluster_state_observer
445
+ @command_runner = run_command_runner
446
+ end
294
447
 
295
448
  trap_signals
296
449
  @loop.run
297
- @serf.stop if @serf.running?
298
450
 
299
- @service_runner.success?
451
+ while @service_runner.nil? do
452
+ sleep 1
453
+ end
454
+
455
+ succeeded = @service_runner.success?
456
+ logger.trace("run_internal: done")
457
+ succeeded
300
458
  end
301
459
 
302
- private
303
460
  def setup_initial_on_ready
304
461
  return if @configuration.ready_notify_fd.nil?
305
462
  @service_runner.on_ready = lambda do
@@ -334,36 +491,74 @@ module Droonga
334
491
  end
335
492
 
336
493
  def stop_gracefully
337
- @command_runner.stop
338
- @serf.stop
339
- @catalog_observer.stop
340
- @service_runner.stop_gracefully
494
+ logger.trace("stop_gracefully: start")
495
+ logger.trace("stop_gracefully: stopping serf agent")
496
+ stop_serf do
497
+ logger.trace("stop_gracefully: stopping command runner")
498
+ @command_runner.stop
499
+ logger.trace("stop_gracefully: stopping cluster_state_observer")
500
+ @cluster_state_observer.stop
501
+ logger.trace("stop_gracefully: stopping catalog_observer")
502
+ @catalog_observer.stop
503
+ @service_runner.stop_gracefully
504
+ logger.trace("stop_gracefully: completely done")
505
+ end
506
+ logger.trace("stop_gracefully: done")
341
507
  end
342
508
 
343
509
  def stop_immediately
344
- @command_runner.stop
345
- @serf.stop
346
- @catalog_observer.stop
347
- @service_runner.stop_immediately
510
+ stop_serf do
511
+ @command_runner.stop
512
+ @cluster_state_observer.stop
513
+ @catalog_observer.stop
514
+ @service_runner.stop_immediately
515
+ end
348
516
  end
349
517
 
350
518
  def restart_graceful
519
+ return if @restarting
520
+ @restarting = true
521
+ logger.trace("restart_graceful: start")
351
522
  old_service_runner = @service_runner
523
+ reopen_log_file
352
524
  @service_runner = run_service
353
525
  @service_runner.on_ready = lambda do
526
+ logger.info("restart_graceful: new service runner is ready")
354
527
  @service_runner.on_failure = nil
528
+ @service_runner.refresh_self_reference
355
529
  old_service_runner.stop_gracefully
530
+ @restarting = false
531
+ logger.trace("restart_graceful: done")
356
532
  end
357
533
  @service_runner.on_failure = lambda do
534
+ logger.info("restart_graceful: failed to setup new service runner")
358
535
  @service_runner.on_failure = nil
359
536
  @service_runner = old_service_runner
537
+ @restarting = false
538
+ logger.trace("restart_graceful: failed")
360
539
  end
361
540
  end
362
541
 
363
542
  def restart_immediately
543
+ return if @restarting
544
+ @restarting = true
364
545
  old_service_runner = @service_runner
546
+ reopen_log_file
365
547
  @service_runner = run_service
366
548
  old_service_runner.stop_immediately
549
+ @restarting = false
550
+ end
551
+
552
+ def restart_self
553
+ logger.trace("restart_self: start")
554
+ old_pid_file_path = Pathname.new("#{@pid_file_path}.old")
555
+ FileUtils.mv(@pid_file_path.to_s, old_pid_file_path.to_s)
556
+ @pid_file_path = old_pid_file_path
557
+ stop_gracefully
558
+
559
+ engine_runner = EngineRunner.new(@configuration)
560
+ engine_runner.run
561
+ logger.trace("restart_self: done")
367
562
  end
368
563
 
369
564
  def run_service
@@ -372,22 +567,68 @@ module Droonga
372
567
  service_runner
373
568
  end
374
569
 
375
- def run_serf
376
- serf = Serf.new(@loop, @configuration.engine_name)
377
- serf.start
378
- serf
570
+ def start_serf
571
+ @serf = Serf.new(@configuration.engine_name)
572
+ @serf_agent = @serf.run_agent(@loop)
573
+ end
574
+
575
+ def stop_serf(&block)
576
+ logger.trace("stop_serf: start")
577
+ begin
578
+ @serf.leave
579
+ rescue Droonga::Serf::Command::Failure
580
+ logger.error("Failed to leave from Serf cluster: #{$!.message}")
581
+ end
582
+ @serf_agent.stop do
583
+ logger.trace("stop_serf: serf agent stopped")
584
+ yield
585
+ end
586
+ logger.trace("stop_serf: done")
379
587
  end
380
588
 
381
589
  def run_catalog_observer
382
590
  catalog_observer = FileObserver.new(@loop, Path.catalog)
383
591
  catalog_observer.on_change = lambda do
592
+ logger.info("restart by updated catalog.json")
384
593
  restart_graceful
385
- @serf.update_cluster_id if @serf and @serf.running?
594
+ @serf.update_cluster_id
386
595
  end
387
596
  catalog_observer.start
388
597
  catalog_observer
389
598
  end
390
599
 
600
+ RESTART_TRIGGER_KEYS = [
601
+ "role",
602
+ "accept_messages_newer_than",
603
+ ]
604
+
605
+ def run_cluster_state_observer
606
+ previous_state = nil
607
+ cluster_state_observer = FileObserver.new(@loop, Path.cluster_state)
608
+ cluster_state_observer.on_change = lambda do
609
+ my_name = @configuration.engine_name
610
+ new_state = Cluster.load_state_file
611
+ if new_state and previous_state
612
+ my_new_state = new_state[my_name].select do |key, _value|
613
+ RESTART_TRIGGER_KEYS.include?(key)
614
+ end
615
+ my_previous_state = previous_state[my_name].select do |key, _value|
616
+ RESTART_TRIGGER_KEYS.include?(key)
617
+ end
618
+ if my_new_state != my_previous_state
619
+ logger.info("restart by changes of myself in cluster-state.json",
620
+ :previous => my_previous_state,
621
+ :new => my_new_state,
622
+ :diff => Differ.diff(my_previous_state, my_new_state))
623
+ restart_graceful
624
+ end
625
+ end
626
+ previous_state = new_state
627
+ end
628
+ cluster_state_observer.start
629
+ cluster_state_observer
630
+ end
631
+
391
632
  def run_command_runner
392
633
  command_runner = CommandRunner.new(@loop)
393
634
  command_runner.on_command = lambda do |command|
@@ -396,23 +637,45 @@ module Droonga
396
637
  command_runner.start
397
638
  command_runner
398
639
  end
640
+
641
+ def log_tag
642
+ "droonga-engine"
643
+ end
399
644
  end
400
645
 
401
- class ServiceRunner
402
- def initialize(raw_loop, configuration)
403
- @raw_loop = raw_loop
646
+ class EngineRunner
647
+ def initialize(configuration)
404
648
  @configuration = configuration
405
- @success = false
406
- @on_ready = nil
407
- @on_failure = nil
408
649
  end
409
650
 
410
- def on_ready=(callback)
411
- @on_ready = callback
651
+ def run
652
+ listen_fd = @configuration.listen_socket.fileno
653
+ heartbeat_fd = @configuration.heartbeat_socket.fileno
654
+ env = {}
655
+ command_line = [
656
+ RbConfig.ruby,
657
+ "-S",
658
+ "droonga-engine",
659
+ "--listen-fd", listen_fd.to_s,
660
+ "--heartbeat-fd", heartbeat_fd.to_s,
661
+ *@configuration.to_engine_command_line,
662
+ ]
663
+ options = {
664
+ listen_fd => listen_fd,
665
+ heartbeat_fd => heartbeat_fd,
666
+ }
667
+ spawn(env, *command_line, options)
412
668
  end
669
+ end
413
670
 
414
- def on_failure=(callback)
415
- @on_failure = callback
671
+ class ServiceRunner
672
+ include Loggable
673
+ include Deferrable
674
+
675
+ def initialize(raw_loop, configuration)
676
+ @raw_loop = raw_loop
677
+ @configuration = configuration
678
+ @success = false
416
679
  end
417
680
 
418
681
  def run
@@ -429,7 +692,7 @@ module Droonga
429
692
  "--heartbeat-fd", heartbeat_fd.to_s,
430
693
  "--control-read-fd", control_write_in.fileno.to_s,
431
694
  "--control-write-fd", control_read_out.fileno.to_s,
432
- *@configuration.to_command_line,
695
+ *@configuration.to_service_command_line,
433
696
  ]
434
697
  options = {
435
698
  listen_fd => listen_fd,
@@ -446,17 +709,25 @@ module Droonga
446
709
  end
447
710
 
448
711
  def stop_gracefully
712
+ logger.trace("stop_gracefully: start")
449
713
  @supervisor.stop_gracefully
714
+ logger.trace("stop_gracefully: done")
450
715
  end
451
716
 
452
717
  def stop_immediately
718
+ logger.trace("stop_immediately: start")
453
719
  @supervisor.stop_immediately
720
+ logger.trace("stop_immediately: done")
454
721
  end
455
722
 
456
723
  def success?
457
724
  @success
458
725
  end
459
726
 
727
+ def refresh_self_reference
728
+ @supervisor.refresh_self_reference
729
+ end
730
+
460
731
  private
461
732
  def create_process_supervisor(input, output)
462
733
  supervisor = ProcessSupervisor.new(@raw_loop, input, output)
@@ -469,23 +740,21 @@ module Droonga
469
740
  supervisor
470
741
  end
471
742
 
472
- def on_ready
473
- @on_ready.call if @on_ready
474
- end
475
-
476
- def on_failure
477
- @on_failure.call if @on_failure
478
- end
479
-
480
743
  def on_finish
481
744
  _, status = Process.waitpid2(@pid)
482
745
  @success = status.success?
483
746
  @supervisor.stop
484
747
  on_failure unless success?
485
748
  end
749
+
750
+ def log_tag
751
+ "service_runner"
752
+ end
486
753
  end
487
754
 
488
755
  class CommandRunner
756
+ include Loggable
757
+
489
758
  attr_writer :on_command
490
759
  def initialize(loop)
491
760
  @loop = loop
@@ -494,8 +763,9 @@ module Droonga
494
763
  end
495
764
 
496
765
  def start
766
+ logger.trace("start: stert")
497
767
  @async_watcher = Coolio::AsyncWatcher.new
498
- on_signal = lambda do
768
+ @async_watcher.on_signal do
499
769
  commands = @commands.uniq
500
770
  @commands.clear
501
771
  until commands.empty?
@@ -503,16 +773,19 @@ module Droonga
503
773
  @on_command.call(command) if @on_command
504
774
  end
505
775
  end
506
- @async_watcher.on_signal do
507
- on_signal.call
508
- end
509
776
  @loop.attach(@async_watcher)
777
+ logger.trace("start: async watcher attached",
778
+ :watcher => @async_watcher)
779
+ logger.trace("start: done")
510
780
  end
511
781
 
512
782
  def stop
513
783
  return if @async_watcher.nil?
784
+ logger.trace("stop: stert")
514
785
  @async_watcher.detach
786
+ # logger.trace("stop: watcher detached", :watcher => @async_watcher)
515
787
  @async_watcher = nil
788
+ logger.trace("stop: done")
516
789
  end
517
790
 
518
791
  def push_command(command)
@@ -521,6 +794,10 @@ module Droonga
521
794
  @commands << command
522
795
  @async_watcher.signal if first_command_p
523
796
  end
797
+
798
+ def log_tag
799
+ "command_runner"
800
+ end
524
801
  end
525
802
  end
526
803
  end