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
@@ -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
@@ -15,9 +15,8 @@
15
15
 
16
16
  require "English"
17
17
 
18
- require "coolio"
19
-
20
18
  require "droonga/loggable"
19
+ require "droonga/deferrable"
21
20
  require "droonga/event_loop"
22
21
  require "droonga/forwarder"
23
22
  require "droonga/replier"
@@ -25,28 +24,32 @@ require "droonga/replier"
25
24
  module Droonga
26
25
  class EngineState
27
26
  include Loggable
27
+ include Deferrable
28
+
29
+ DEFAULT_SESSION_TIMEOUT_SECONDS = 60
28
30
 
29
31
  attr_reader :loop
30
32
  attr_reader :name
31
33
  attr_reader :internal_name
34
+ attr_reader :internal_connection_lifetime
32
35
  attr_reader :forwarder
33
36
  attr_reader :replier
34
- attr_writer :on_ready
35
- attr_accessor :on_finish
36
37
  attr_accessor :catalog
37
- def initialize(loop, name, internal_name)
38
+ attr_accessor :on_finish
39
+
40
+ def initialize(loop, name, internal_name, params)
38
41
  @loop = loop
39
42
  @name = name
40
43
  @internal_name = internal_name
44
+ @internal_connection_lifetime = params[:internal_connection_lifetime]
41
45
  @sessions = {}
42
46
  @current_id = 0
43
- @forwarder = Forwarder.new(@loop, :buffering => true)
47
+ @forwarder = Forwarder.new(@loop,
48
+ :auto_close_timeout =>
49
+ @internal_connection_lifetime)
44
50
  @replier = Replier.new(@forwarder)
45
- @on_ready = nil
46
51
  @on_finish = nil
47
- @catalog = nil
48
- @live_nodes = nil
49
- @dead_nodes = []
52
+ @catalog = params[:catalog]
50
53
  end
51
54
 
52
55
  def start
@@ -65,8 +68,43 @@ module Droonga
65
68
  route.start_with?(@name) or route.start_with?(@internal_name)
66
69
  end
67
70
 
68
- def farm_path(route)
69
- if /\A[^:]+:\d+\/[^.]+/ =~ route
71
+ FARM_PATH_MATCHER = /\A[^:]+:\d+\/[^.]+/
72
+
73
+ def internal_route(route)
74
+ if FARM_PATH_MATCHER =~ route
75
+ name = $MATCH
76
+ if name == @name or name == @internal_name
77
+ return route.sub(name, @internal_name)
78
+ end
79
+ end
80
+ route
81
+ end
82
+
83
+ def public_route(route)
84
+ if FARM_PATH_MATCHER =~ route
85
+ name = $MATCH
86
+ if name == @internal_name
87
+ return route.sub(name, @name)
88
+ end
89
+ end
90
+ route
91
+ end
92
+
93
+ def internal_farm_path(route)
94
+ if FARM_PATH_MATCHER =~ route
95
+ name = $MATCH
96
+ if name == @name or name == @internal_name
97
+ @internal_name
98
+ else
99
+ name
100
+ end
101
+ else
102
+ route
103
+ end
104
+ end
105
+
106
+ def public_farm_path(route)
107
+ if FARM_PATH_MATCHER =~ route
70
108
  name = $MATCH
71
109
  if name == @internal_name
72
110
  @name
@@ -88,12 +126,22 @@ module Droonga
88
126
  @sessions[id]
89
127
  end
90
128
 
91
- def register_session(id, session)
129
+ def register_session(id, session, options={})
92
130
  @sessions[id] = session
93
131
  logger.trace("new session #{id} is registered. rest sessions=#{@sessions.size}")
132
+
133
+ timeout = options[:timeout] ||
134
+ @internal_connection_lifetime ||
135
+ DEFAULT_SESSION_TIMEOUT_SECONDS
136
+ session.set_timeout(@loop, timeout) do
137
+ logger.trace("session #{id} is timed out!")
138
+ unregister_session(id)
139
+ end
94
140
  end
95
141
 
96
142
  def unregister_session(id)
143
+ session = @sessions[id]
144
+ session.finish
97
145
  @sessions.delete(id)
98
146
  unless have_session?
99
147
  @on_finish.call if @on_finish
@@ -105,32 +153,6 @@ module Droonga
105
153
  not @sessions.empty?
106
154
  end
107
155
 
108
- def all_nodes
109
- @catalog.all_nodes
110
- end
111
-
112
- def live_nodes
113
- @live_nodes || @catalog.all_nodes
114
- end
115
-
116
- def live_nodes=(nodes)
117
- old_live_nodes = @live_nodes
118
- @live_nodes = nodes
119
- @dead_nodes = all_nodes - @live_nodes
120
- @forwarder.resume if old_live_nodes != @live_nodes
121
- @live_nodes
122
- end
123
-
124
- def remove_dead_routes(routes)
125
- routes.reject do |route|
126
- @dead_nodes.include?(farm_path(route))
127
- end
128
- end
129
-
130
- def on_ready
131
- @on_ready.call if @on_ready
132
- end
133
-
134
156
  private
135
157
  def log_tag
136
158
  "engine_state"
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2013 Droonga Project
3
+ # Copyright (C) 2013-2015 Droonga Project
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -15,11 +15,22 @@
15
15
  # License along with this library; if not, write to the Free Software
16
16
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
+ require "droonga/loggable"
19
+ require "droonga/deferrable"
18
20
  require "droonga/slice"
19
21
 
20
22
  module Droonga
21
23
  class Farm
22
- attr_writer :on_ready
24
+ include Loggable
25
+ include Deferrable
26
+
27
+ class NoSlice < StandardError
28
+ def initialize(message, extra_informations={})
29
+ message = "#{message}: #{extra_informations.inspect}"
30
+ super(message)
31
+ end
32
+ end
33
+
23
34
  def initialize(name, catalog, loop, options={})
24
35
  @name = name
25
36
  @catalog = catalog
@@ -29,7 +40,7 @@ module Droonga
29
40
  slices = @catalog.slices(name)
30
41
  slices.each do |slice_name, slice_options|
31
42
  dataset = @catalog.datasets[slice_options[:dataset]]
32
- slice = Droonga::Slice.new(dataset,
43
+ slice = Droonga::Slice.new(slice_name, dataset,
33
44
  @loop,
34
45
  @options.merge(slice_options))
35
46
  @slices[slice_name] = slice
@@ -37,12 +48,18 @@ module Droonga
37
48
  end
38
49
 
39
50
  def start
51
+ n_slices = @slices.size
52
+ if n_slices.zero?
53
+ on_ready
54
+ return
55
+ end
56
+
40
57
  n_ready_slices = 0
41
58
  @slices.each_value do |slice|
42
59
  slice.on_ready = lambda do
43
60
  n_ready_slices += 1
44
- if n_ready_slices == @slices.size
45
- @on_ready.call if @on_ready
61
+ if n_ready_slices == n_slices
62
+ on_ready
46
63
  end
47
64
  end
48
65
  slice.start
@@ -50,13 +67,21 @@ module Droonga
50
67
  end
51
68
 
52
69
  def stop_gracefully
70
+ logger.trace("stop_gracefully: start")
53
71
  n_slices = @slices.size
72
+ if n_slices.zero?
73
+ yield if block_given?
74
+ logger.trace("stop_gracefully: done")
75
+ return
76
+ end
77
+
54
78
  n_done_slices = 0
55
79
  @slices.each_value do |slice|
56
80
  slice.stop_gracefully do
57
81
  n_done_slices += 1
58
82
  if n_done_slices == n_slices
59
83
  yield if block_given?
84
+ logger.trace("stop_gracefully: done")
60
85
  end
61
86
  end
62
87
  end
@@ -68,8 +93,22 @@ module Droonga
68
93
  end
69
94
  end
70
95
 
96
+ def refresh_node_reference
97
+ @slices.each_value do |slice|
98
+ slice.refresh_node_reference
99
+ end
100
+ end
101
+
71
102
  def process(slice_name, message)
103
+ unless @slices.key?(slice_name)
104
+ raise NoSlice.new(slice_name, :message => message, :slices => @slices.keys)
105
+ end
72
106
  @slices[slice_name].process(message)
73
107
  end
108
+
109
+ private
110
+ def log_tag
111
+ "farm"
112
+ end
74
113
  end
75
114
  end
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2014 Droonga Project
3
+ # Copyright (C) 2014-2015 Droonga Project
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -19,15 +19,15 @@ require "coolio"
19
19
 
20
20
  require "droonga/path"
21
21
  require "droonga/loggable"
22
+ require "droonga/changable"
22
23
 
23
24
  module Droonga
24
25
  class FileObserver
25
26
  include Loggable
27
+ include Changable
26
28
 
27
29
  CHECK_INTERVAL = 1
28
30
 
29
- attr_accessor :on_change
30
-
31
31
  def initialize(loop, path)
32
32
  @loop = loop
33
33
  @path = path
@@ -36,25 +36,29 @@ module Droonga
36
36
  else
37
37
  @mtime = nil
38
38
  end
39
- @on_change = nil
40
39
  end
41
40
 
42
41
  def start
43
- @watcher = Coolio::TimerWatcher.new(CHECK_INTERVAL, true)
44
- on_timer = lambda do
42
+ return if @timer
43
+ @timer = Coolio::TimerWatcher.new(CHECK_INTERVAL, true)
44
+ @timer.on_timer do
45
45
  if updated?
46
46
  @mtime = @path.mtime
47
- @on_change.call if @on_change
47
+ on_change
48
48
  end
49
49
  end
50
- @watcher.on_timer do
51
- on_timer.call
52
- end
53
- @loop.attach(@watcher)
50
+ @loop.attach(@timer)
51
+ logger.trace("start: timer attached",
52
+ :watcher => @timer,
53
+ :path => @path)
54
54
  end
55
55
 
56
56
  def stop
57
- @watcher.detach
57
+ @timer.detach if @timer
58
+ logger.trace("stop: timer detached",
59
+ :watcher => @timer,
60
+ :path => @path)
61
+ @timer = nil
58
62
  end
59
63
 
60
64
  private
@@ -24,6 +24,21 @@ module Droonga
24
24
  class FluentMessageReceiver
25
25
  include Loggable
26
26
 
27
+ class InvalidObject < StandardError
28
+ def initialize(object)
29
+ message = "no valid tag information: #{object.inspect}"
30
+ super(message)
31
+ end
32
+ end
33
+
34
+ class UnknownTypeEntries < StandardError
35
+ def initialize(object)
36
+ message = "unknown type message: couldn't detect entries: " +
37
+ "#{object.inspect}"
38
+ super(message)
39
+ end
40
+ end
41
+
27
42
  def initialize(loop, options={}, &on_message)
28
43
  @loop = loop
29
44
  @listen_fd = options[:listen_fd]
@@ -31,6 +46,7 @@ module Droonga
31
46
  @server = nil
32
47
  @clients = []
33
48
  @on_message = on_message
49
+ @on_shutdown_ready = nil
34
50
  end
35
51
 
36
52
  def start
@@ -48,20 +64,34 @@ module Droonga
48
64
  logger.trace("stop_gracefully: done")
49
65
  end
50
66
 
67
+ def ensure_no_client(&block)
68
+ if @clients.empty?
69
+ logger.trace("ensure_no_client: no client")
70
+ yield
71
+ elsif block_given?
72
+ logger.trace("ensure_no_client: waiting for #{@clients.size} clients to be disconnected",
73
+ :clients => @clients)
74
+ @on_shutdown_ready = lambda do
75
+ logger.trace("ensure_no_client: all clients are disconnected")
76
+ yield
77
+ end
78
+ end
79
+ end
80
+
51
81
  def stop_immediately
52
82
  logger.trace("stop_immediately: start")
53
83
  stop_gracefully
54
- shutdown_clients
84
+ force_shutdown_clients
55
85
  logger.trace("stop_immediately: done")
56
86
  end
57
87
 
58
- def shutdown_clients
88
+ private
89
+ def force_shutdown_clients
59
90
  @clients.dup.each do |client|
60
91
  client.close
61
92
  end
62
93
  end
63
94
 
64
- private
65
95
  def start_heartbeat_receiver
66
96
  logger.trace("start_heartbeat_receiver: start")
67
97
  @heartbeat_receiver = HeartbeatReceiver.new(@loop, @heartbeat_fd)
@@ -80,15 +110,29 @@ module Droonga
80
110
 
81
111
  @clients = []
82
112
  @server = create_server do |connection|
113
+ logger.trace("Client: new connection", :connection => connection)
83
114
  client = Client.new(connection) do |tag, time, record|
115
+ logger.trace("Client: on_message: start")
84
116
  @on_message.call(tag, time, record)
117
+ logger.trace("Client: on_message: done")
85
118
  end
86
119
  client.on_close = lambda do
87
120
  @clients.delete(client)
121
+ if @on_shutdown_ready
122
+ logger.trace("Client: a client is disconnected. still waiting for #{@clients.size} clients.",
123
+ :clients => @clients)
124
+ if @clients.empty?
125
+ @on_shutdown_ready.call
126
+ end
127
+ end
88
128
  end
89
129
  @clients << client
90
130
  end
91
131
  @loop.attach(@server)
132
+ logger.trace("start_server: server watcher attached",
133
+ :watcher => @server,
134
+ :listen_fd => @listen_fd,
135
+ :heartbeat_fd => @heartbeat_fd)
92
136
 
93
137
  logger.trace("start_server: done")
94
138
  end
@@ -100,6 +144,8 @@ module Droonga
100
144
  def shutdown_server
101
145
  logger.trace("shutdown_server: start")
102
146
  @server.close
147
+ logger.trace("shutdown_server: server watcher detached",
148
+ :watcher => @server)
103
149
  logger.trace("shutdown_server: done")
104
150
  end
105
151
 
@@ -108,6 +154,8 @@ module Droonga
108
154
  end
109
155
 
110
156
  class HeartbeatReceiver
157
+ include Loggable
158
+
111
159
  def initialize(loop, fd)
112
160
  @loop = loop
113
161
  @fd = fd
@@ -117,18 +165,21 @@ module Droonga
117
165
  @socket = UDPSocket.for_fd(@fd)
118
166
 
119
167
  @watcher = Coolio::IOWatcher.new(@socket, "r")
120
- on_readable = lambda do
121
- receive_heartbeat
122
- end
123
168
  @watcher.on_readable do
124
- on_readable.call
169
+ receive_heartbeat
125
170
  end
126
171
  @loop.attach(@watcher)
172
+ logger.trace("start: heartbeat IO watcher attached",
173
+ :watcher => @watcher,
174
+ :fd => @fd)
127
175
  end
128
176
 
129
177
  def shutdown
130
178
  @socket.close
131
179
  @watcher.detach
180
+ logger.trace("shutdown: heartbeat watcher detached",
181
+ :watcher => @watcher,
182
+ :fd => @fd)
132
183
  end
133
184
 
134
185
  private
@@ -153,6 +204,10 @@ module Droonga
153
204
  rescue SystemCallError
154
205
  end
155
206
  end
207
+
208
+ def log_tag
209
+ "heartbeat-receiver"
210
+ end
156
211
  end
157
212
 
158
213
  class Client
@@ -165,20 +220,14 @@ module Droonga
165
220
  @on_close = nil
166
221
  @unpacker = MessagePack::Unpacker.new
167
222
 
168
- on_read = lambda do |data|
169
- feed(data)
170
- end
171
223
  @io.on_read do |data|
172
- on_read.call(data)
224
+ feed(data)
173
225
  end
174
226
 
175
- on_close = lambda do
227
+ @io.on_close do
176
228
  @io = nil
177
229
  @on_close.call if @on_close
178
230
  end
179
- @io.on_close do
180
- on_close.call
181
- end
182
231
  end
183
232
 
184
233
  def close
@@ -187,22 +236,42 @@ module Droonga
187
236
 
188
237
  private
189
238
  def feed(data)
239
+ logger.trace("Client: feed: start", :data => data)
190
240
  @unpacker.feed_each(data) do |object|
191
- tag = object[0]
192
- case object[1]
193
- when String # PackedForward message
194
- entries = MessagePack.unpack(object[1])
195
- when Array # Forward message
196
- entries = object[1]
197
- when Integer, Float # Message message
198
- entries = [[object[1], object[2]]]
199
- else
200
- logger.error("unknown message", :message => object)
201
- next
202
- end
203
- entries.each do |time, record|
204
- @on_message.call(tag, time || Time.now.to_i, record)
241
+ logger.trace("Client: feed_each: start", :object => object)
242
+ begin
243
+ handle_feeded_object(object)
244
+ rescue InvalidObject, UnknownTypeEntries, MessagePack::MalformedFormatError => error
245
+ logger.exception("failed to process feeded object", error)
205
246
  end
247
+ logger.trace("Client: feed_each: done")
248
+ end
249
+ logger.trace("Client: feed: done")
250
+ end
251
+
252
+ def handle_feeded_object(object)
253
+ raise InvalidObject.new(object) unless object.is_a?(Array)
254
+
255
+ tag = object[0]
256
+ raise InvalidObject.new(object) unless tag.is_a?(String)
257
+
258
+ case object[1]
259
+ when String # PackedForward message [tag, "packed entries"]
260
+ raise InvalidObject.new(object) unless object.size == 2
261
+ entries = MessagePack.unpack(object[1])
262
+ when Array # Forward message [tag, [entry, entry...]]
263
+ raise InvalidObject.new(object) unless object.size == 2
264
+ entries = object[1]
265
+ when Integer, Float # Message message [tag, time, record]
266
+ raise InvalidObject.new(object) unless object.size == 3
267
+ entries = [[object[1], object[2]]]
268
+ else
269
+ raise UnknownTypeEntries.new(object)
270
+ end
271
+
272
+ raise InvalidObject.new(object) unless entries.is_a?(Array)
273
+ entries.each do |time, record|
274
+ @on_message.call(tag, time || Time.now.to_i, record)
206
275
  end
207
276
  end
208
277