tengine_core 0.5.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +40 -0
  4. data/Gemfile.lock +95 -0
  5. data/README.md +54 -0
  6. data/Rakefile +44 -0
  7. data/VERSION +1 -0
  8. data/bin/tengine_atd +8 -0
  9. data/bin/tengine_heartbeat_watchd +8 -0
  10. data/bin/tengined +182 -0
  11. data/examples/VERSION +1 -0
  12. data/examples/uc01_execute_processing_for_event.rb +11 -0
  13. data/examples/uc02_fire_another_event.rb +16 -0
  14. data/examples/uc03_2handlers_for_1event.rb +16 -0
  15. data/examples/uc08_if_both_a_and_b_occurs.rb +11 -0
  16. data/examples/uc10_if_the_event_occurs_at_the_server.rb +15 -0
  17. data/examples/uc50_commit_event_at_first.rb +17 -0
  18. data/examples/uc51_commit_event_at_first_submit.rb +29 -0
  19. data/examples/uc52_commit_event_after_all_handler_submit.rb +31 -0
  20. data/examples/uc52_never_commit_event_unless_all_handler_submit.rb +31 -0
  21. data/examples/uc60_event_in_handler.rb +18 -0
  22. data/examples/uc62_session_in_driver.rb +16 -0
  23. data/examples/uc64_safety_countup.rb +14 -0
  24. data/examples/uc70_driver_enabled_on_activation.rb +13 -0
  25. data/examples/uc71_driver_disabled_on_activation.rb +14 -0
  26. data/examples/uc72_setup_eventmachine.rb +17 -0
  27. data/examples/uc80_raise_io_error.rb +10 -0
  28. data/examples/uc81_raise_runtime_error.rb +10 -0
  29. data/examples2/driver01.rb +18 -0
  30. data/examples2/driver02.rb +19 -0
  31. data/examples2/uc08_if_both_a_and_b_occurs.rb +13 -0
  32. data/examples2/uc10_if_the_event_occurs_at_the_server.rb +18 -0
  33. data/examples2/uc51_commit_event_at_first_submit_1.rb +16 -0
  34. data/examples2/uc51_commit_event_at_first_submit_2.rb +17 -0
  35. data/examples2/uc51_commit_event_at_first_submit_3.rb +17 -0
  36. data/examples2/uc62_session_in_driver.rb +16 -0
  37. data/examples2/uc71_driver_disabled_on_activation.rb +16 -0
  38. data/failure_examples/VERSION +1 -0
  39. data/failure_examples/uc53_submit_outside_of_handler.rb +15 -0
  40. data/failure_examples/uc61_event_outside_of_handler.rb +12 -0
  41. data/failure_examples/uc63_session_outside_of_driver.rb +13 -0
  42. data/lib/tengine/core.rb +74 -0
  43. data/lib/tengine/core/bootstrap.rb +123 -0
  44. data/lib/tengine/core/collection_accessible.rb +34 -0
  45. data/lib/tengine/core/config.rb +10 -0
  46. data/lib/tengine/core/config/atd.rb +225 -0
  47. data/lib/tengine/core/config/core.rb +319 -0
  48. data/lib/tengine/core/config/heartbeat_watcher.rb +229 -0
  49. data/lib/tengine/core/connection_test/.gitignore +1 -0
  50. data/lib/tengine/core/connection_test/fire_bar_on_foo.rb +16 -0
  51. data/lib/tengine/core/driveable.rb +213 -0
  52. data/lib/tengine/core/driver.rb +69 -0
  53. data/lib/tengine/core/driver/finder.rb +42 -0
  54. data/lib/tengine/core/dsl_evaluator.rb +110 -0
  55. data/lib/tengine/core/dsl_filter_def.rb +11 -0
  56. data/lib/tengine/core/dsl_loader.rb +108 -0
  57. data/lib/tengine/core/event.rb +145 -0
  58. data/lib/tengine/core/event/finder.rb +82 -0
  59. data/lib/tengine/core/event_exception_reportable.rb +88 -0
  60. data/lib/tengine/core/event_wrapper.rb +21 -0
  61. data/lib/tengine/core/find_by_name.rb +31 -0
  62. data/lib/tengine/core/handler.rb +152 -0
  63. data/lib/tengine/core/handler_path.rb +33 -0
  64. data/lib/tengine/core/heartbeat_watcher.rb +161 -0
  65. data/lib/tengine/core/io_to_logger.rb +22 -0
  66. data/lib/tengine/core/kernel.rb +510 -0
  67. data/lib/tengine/core/kernel_runtime.rb +91 -0
  68. data/lib/tengine/core/method_traceable.rb +38 -0
  69. data/lib/tengine/core/mongoid_fix.rb +19 -0
  70. data/lib/tengine/core/mutex.rb +177 -0
  71. data/lib/tengine/core/optimistic_lock.rb +69 -0
  72. data/lib/tengine/core/plugins.rb +54 -0
  73. data/lib/tengine/core/schedule.rb +21 -0
  74. data/lib/tengine/core/scheduler.rb +156 -0
  75. data/lib/tengine/core/selectable_attr.rb +29 -0
  76. data/lib/tengine/core/session.rb +21 -0
  77. data/lib/tengine/core/session_wrapper.rb +68 -0
  78. data/lib/tengine/core/setting.rb +21 -0
  79. data/lib/tengine/core/validation.rb +36 -0
  80. data/lib/tengine/errors.rb +18 -0
  81. data/lib/tengine/rspec.rb +8 -0
  82. data/lib/tengine/rspec/context_wrapper.rb +51 -0
  83. data/lib/tengine/rspec/extension.rb +53 -0
  84. data/lib/tengine_core.rb +23 -0
  85. data/spec/factories/tengine_core_drivers.rb +10 -0
  86. data/spec/factories/tengine_core_events.rb +14 -0
  87. data/spec/factories/tengine_core_handler_paths.rb +9 -0
  88. data/spec/factories/tengine_core_handlers.rb +9 -0
  89. data/spec/factories/tengine_core_sessions.rb +9 -0
  90. data/spec/mongoid.yml +35 -0
  91. data/spec/spec_helper.rb +48 -0
  92. data/spec/support/mongo_index_key_log.rb +91 -0
  93. data/spec/tengine/core/bootstrap_spec.rb +278 -0
  94. data/spec/tengine/core/bugfix/bind_dsl_file_in_multi_byte_dir_spec.rb +21 -0
  95. data/spec/tengine/core/bugfix/enabled_on_activation_spec.rb +112 -0
  96. data/spec/tengine/core/bugfix/receive_event_spec.rb +133 -0
  97. data/spec/tengine/core/bugfix/use_dsl_version_method.rb +12 -0
  98. data/spec/tengine/core/bugfix/use_dsl_version_method_spec.rb +28 -0
  99. data/spec/tengine/core/bugfix/use_event_in_handler_dsl.rb +11 -0
  100. data/spec/tengine/core/bugfix//351/235/236ACSII/343/201/256/343/203/206/343/202/231/343/202/243/343/203/254/343/202/257/343/203/210/343/203/252/345/220/215/source_location_encoding.rb +35 -0
  101. data/spec/tengine/core/bugfix//351/235/236ACSII/343/201/256/343/203/206/343/202/231/343/202/243/343/203/254/343/202/257/343/203/210/343/203/252/345/220/215//351/235/236ASCII/343/201/256/343/203/225/343/202/241/343/202/244/343/203/253/345/220/215_dsl.rb +38 -0
  102. data/spec/tengine/core/bugfix//351/235/236ACSII/343/201/256/343/203/207/343/202/243/343/203/254/343/202/257/343/203/210/343/203/252/345/220/215/source_location_encoding.rb +35 -0
  103. data/spec/tengine/core/bugfix//351/235/236ACSII/343/201/256/343/203/207/343/202/243/343/203/254/343/202/257/343/203/210/343/203/252/345/220/215//351/235/236ASCII/343/201/256/343/203/225/343/202/241/343/202/244/343/203/253/345/220/215_dsl.rb +38 -0
  104. data/spec/tengine/core/config/atd_spec.rb +62 -0
  105. data/spec/tengine/core/config/core_spec.rb +479 -0
  106. data/spec/tengine/core/config/heartbeat_watcher_spec.rb +62 -0
  107. data/spec/tengine/core/config/syntax_error_in_erb.yml.erb +13 -0
  108. data/spec/tengine/core/config/wrong_category_name.yml.erb +13 -0
  109. data/spec/tengine/core/config/wrong_field_name.yml.erb +12 -0
  110. data/spec/tengine/core/config/wrong_yaml.yml.erb +13 -0
  111. data/spec/tengine/core/config_spec/another_port.yml +54 -0
  112. data/spec/tengine/core/config_spec/config_with_dir_absolute_load_path.yml +16 -0
  113. data/spec/tengine/core/config_spec/config_with_dir_relative_load_path.yml +16 -0
  114. data/spec/tengine/core/config_spec/config_with_file_absolute_load_path.yml +16 -0
  115. data/spec/tengine/core/config_spec/config_with_file_relative_load_path.yml +16 -0
  116. data/spec/tengine/core/config_spec/log_config_spec.rb +235 -0
  117. data/spec/tengine/core/driveable_spec.rb +240 -0
  118. data/spec/tengine/core/driver_spec.rb +159 -0
  119. data/spec/tengine/core/dsl_loader_spec.rb +172 -0
  120. data/spec/tengine/core/dsls/uc08_if_both_a_and_b_occurs_spec.rb +35 -0
  121. data/spec/tengine/core/dsls/uc10_if_the_event_occurs_at_the_server_spec.rb +58 -0
  122. data/spec/tengine/core/dsls/uc50_commit_event_at_first_spec.rb +29 -0
  123. data/spec/tengine/core/dsls/uc52_commit_event_after_all_handler_submit_spec.rb +33 -0
  124. data/spec/tengine/core/dsls/uc52_never_commit_event_unless_all_handler_submit_spec.rb +37 -0
  125. data/spec/tengine/core/dsls/uc53_submit_outside_of_handler_spec.rb +37 -0
  126. data/spec/tengine/core/dsls/uc60_event_in_handler_spec.rb +31 -0
  127. data/spec/tengine/core/dsls/uc61_event_outside_of_handler_spec.rb +37 -0
  128. data/spec/tengine/core/dsls/uc62_session_in_driver_spec.rb +36 -0
  129. data/spec/tengine/core/dsls/uc63_session_outside_of_driver_spec.rb +35 -0
  130. data/spec/tengine/core/dsls/uc64_safety_countup_spec.rb +134 -0
  131. data/spec/tengine/core/dsls/uc70_driver_enabled_on_activation_spec.rb +39 -0
  132. data/spec/tengine/core/dsls/uc71_driver_disabled_on_activation_spec.rb +36 -0
  133. data/spec/tengine/core/dsls/uc72_setup_eventmachine_spec.rb +39 -0
  134. data/spec/tengine/core/dsls/uc80_raise_io_error_spec.rb +53 -0
  135. data/spec/tengine/core/dsls/uc81_raise_runtime_error_spec.rb +49 -0
  136. data/spec/tengine/core/event/finder_spec.rb +136 -0
  137. data/spec/tengine/core/event_exception_reportable_spec.rb +33 -0
  138. data/spec/tengine/core/event_spec.rb +161 -0
  139. data/spec/tengine/core/event_wrapper_spec.rb +35 -0
  140. data/spec/tengine/core/handler_path_spec.rb +87 -0
  141. data/spec/tengine/core/handler_spec.rb +190 -0
  142. data/spec/tengine/core/heartbeat_watcher_spec.rb +131 -0
  143. data/spec/tengine/core/io_to_logger_spec.rb +30 -0
  144. data/spec/tengine/core/kernel_spec.rb +885 -0
  145. data/spec/tengine/core/mutex_spec.rb +184 -0
  146. data/spec/tengine/core/optimistic_lock_spec.rb +55 -0
  147. data/spec/tengine/core/scheculer_spec.rb +121 -0
  148. data/spec/tengine/core/selectable_attr_spec.rb +30 -0
  149. data/spec/tengine/core/session_spec.rb +104 -0
  150. data/spec/tengine/core/setting_spec.rb +79 -0
  151. data/spec/tengine/core_spec.rb +13 -0
  152. data/spec/tengine_spec.rb +14 -0
  153. data/tengine_core.gemspec +248 -0
  154. data/tmp/log/.gitignore +1 -0
  155. data/tmp/tengined_status/.gitignore +1 -0
  156. metadata +421 -0
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/core'
3
+
4
+ module Tengine::Core::FindByName
5
+ extend ActiveSupport::Concern
6
+
7
+ class Error < Tengine::Errors::NotFound
8
+ attr_reader :klass, :name, :options
9
+ def initialize(klass, name, options = nil)
10
+ @klass, @name, @options = klass, name, options
11
+ end
12
+ def message
13
+ result = "#{klass.name} named #{name.inspect}"
14
+ result << " with #{options.inspect}" if options && !options.empty?
15
+ result << ' not found'
16
+ result
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ def find_by_name(name)
22
+ first(:conditions => {:name => name})
23
+ end
24
+
25
+ def find_by_name!(name, *args, &block)
26
+ result = find_by_name(name, *args, &block)
27
+ raise Error.new(self, name, args.last) unless result
28
+ result
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,152 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/core'
3
+
4
+ require 'tengine/event'
5
+
6
+ # イベントハンドラ
7
+ #
8
+ # Tengineコアは、イベントを受信するとそのイベント種別名にマッチするイベントハンドラを探して
9
+ # 見つかったイベントハンドラをすべて実行します。
10
+ class Tengine::Core::Handler
11
+ include Mongoid::Document
12
+ include Mongoid::Timestamps
13
+ include Tengine::Core::CollectionAccessible
14
+ include Tengine::Core::SelectableAttr
15
+
16
+ # @attribute 実行するRubyのブロックが定義されているファイル名
17
+ field :filepath, :type => String
18
+
19
+ # @attribute 実行するRubyのブロックが定義されているファイルでの行番号
20
+ field :lineno , :type => Integer
21
+
22
+ # @attribute 処理するイベントのイベント種別名の配列
23
+ field :event_type_names, :type => Array
24
+ array_text_accessor :event_type_names
25
+
26
+ # @attribute イベントが対象かどうかを判断するためのフィルタ定義
27
+ field :filter, :type => Hash, :default => {}
28
+ map_yaml_accessor :filter
29
+
30
+ # @attribute 実行対象の取得方法
31
+ field :target_instantiation_cd, :type => String, :default => '01'
32
+
33
+ selectable_attr :target_instantiation_cd do
34
+ entry '01', :binding , "binding"
35
+ entry '02', :static , "static"
36
+ entry '03', :instance_method, "instance_method"
37
+ end
38
+
39
+ # @attribute 実行対象となるメソッドの名前
40
+ field :target_method_name, :type => String
41
+
42
+
43
+ validates :filepath, :presence => true
44
+ validates :lineno , :presence => true
45
+
46
+ embedded_in :driver, :class_name => "Tengine::Core::Driver"
47
+
48
+ def update_handler_path
49
+ event_type_names.each do |event_type_name|
50
+ Tengine::Core::HandlerPath.create!(:event_type_name => event_type_name,
51
+ :driver_id => self.driver.id, :handler_id => self.id)
52
+ end
53
+ end
54
+
55
+ # def process_event(event, &block)
56
+ # @caller = eval("self", block.binding)
57
+ # matched = match?(event)
58
+ # if matched
59
+ # # ハンドラの実行
60
+ # @caller.__safety_driver__(self.driver) do
61
+ # @caller.__safety_event__(event) do
62
+ # @caller.instance_eval(&block)
63
+ # end
64
+ # end
65
+ # end
66
+ # ensure
67
+ # @caller = nil
68
+ # end
69
+
70
+ def process_event(event)
71
+ case self.target_instantiation_key
72
+ when :instance_method then
73
+ klass = driver.target_class_name.constantize
74
+ inst = klass.new
75
+ inst.instance_variable_set(:@__event__, event)
76
+ m = inst.method(target_method_name)
77
+ m.arity == 0 ? m.call : m.call(event)
78
+ when :static then
79
+ klass = driver.target_class_name.constantize
80
+ m = klass.method(target_method_name)
81
+ m.arity == 0 ? m.call : m.call(event)
82
+ when :binding then
83
+ # do nothing
84
+ else
85
+ raise Tengine::Core::KernelError, "Unsupported target_instantiation_key: #{self.target_instantiation_key.inspect}"
86
+ end
87
+ end
88
+
89
+ def fire(event_type_name)
90
+ @caller.fire(event_type_name)
91
+ end
92
+
93
+ def match?(event)
94
+ result = filter.blank? ? true : Visitor.new(filter, event, driver.session).visit
95
+ Tengine.logger.debug("match?(#{event.event_type_name.inspect}) => #{result.inspect}")
96
+ result
97
+ end
98
+
99
+ # HashとArrayで入れ子になったfilterのツリーをルートから各Leafの方向に辿っていくVisitorです。
100
+ # 正確にはVisitorパターンではないのですが、似ているのでメタファとしてVisitorとしました。
101
+ class Visitor
102
+ def initialize(filter, event, session)
103
+ @filter = filter
104
+ @event = event
105
+ @session = Tengine::Core::SessionWrapper.new(session)
106
+ @current = @filter
107
+ end
108
+
109
+ def visit
110
+ Tengine.logger.debug("visiting #{@current.inspect}")
111
+ send(@current['method'])
112
+ end
113
+
114
+ def backup_current(node)
115
+ backup = @current
116
+ @current = node
117
+ begin
118
+ return yield
119
+ ensure
120
+ @current = backup
121
+ end
122
+ end
123
+
124
+ def and
125
+ children = @current["children"]
126
+ # children.all?{|child| backup_current(child){ visit }} # これだと全てのchildrenについて評価せずfalseがあったら処理を抜けてしまいます。
127
+ children.map{|child| backup_current(child){ visit }}.all?
128
+ end
129
+
130
+ def find_or_mark_in_session
131
+ name = @current['pattern'].to_s
132
+ key = "mark_#{name}"
133
+ if name == @event.event_type_name
134
+ unless @session.system_properties[key]
135
+ @session.system_update(key => true)
136
+ Tengine.logger.debug("session.system_updated #{@session.system_properties.inspect}")
137
+ end
138
+ return true
139
+ else
140
+ return @session.system_properties[key]
141
+ end
142
+ end
143
+
144
+ def match_source_name?
145
+ pattern = @current['pattern']
146
+ @event.source_name.include?(pattern)
147
+ end
148
+
149
+ end
150
+
151
+
152
+ end
@@ -0,0 +1,33 @@
1
+ require 'tengine/core'
2
+
3
+ class Tengine::Core::HandlerPath
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+
7
+ field :event_type_name, :type => String
8
+ field :handler_id, :type => Object
9
+
10
+ belongs_to :driver, :index => true, :class_name => "Tengine::Core::Driver"
11
+
12
+ scope(:event_type_name, lambda{|v| where(:event_type_name => v)})
13
+
14
+ index([ [:event_type_name, Mongo::ASCENDING], [:_id, Mongo::ASCENDING], ])
15
+
16
+ class << self
17
+ def find_handlers(event_type_name)
18
+ paths = self.event_type_name(event_type_name).to_a
19
+ driver_id_to_handler_id = paths.inject({}) do |d, path|
20
+ d[path.driver_id] ||= []
21
+ d[path.driver_id] << path.handler_id
22
+ d
23
+ end
24
+ drivers = Tengine::Core::Driver.any_in(:_id => paths.map(&:driver_id)).and(:enabled => true, :version => default_driver_version)
25
+ drivers.map do |driver|
26
+ driver.handlers.any_in(:_id => driver_id_to_handler_id[driver.id])
27
+ end.flatten
28
+ end
29
+
30
+ attr_accessor :default_driver_version
31
+
32
+ end
33
+ end
@@ -0,0 +1,161 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'daemons'
3
+ require 'eventmachine'
4
+ require 'mongoid'
5
+ require 'uuid'
6
+ require 'active_support/core_ext/hash/keys'
7
+
8
+ $LOAD_PATH.push File.expand_path("../../../../lib/", __FILE__)
9
+
10
+ require 'tengine/core'
11
+ require 'tengine/event'
12
+ require 'tengine/mq'
13
+
14
+ # explicit loading
15
+ require_relative 'config/heartbeat_watcher'
16
+ require_relative 'method_traceable'
17
+ require_relative 'mongoid_fix'
18
+
19
+ class Tengine::Core::HeartbeatWatcher
20
+
21
+ def initialize argv
22
+ @uuid = UUID.new.generate
23
+ @config = Tengine::Core::Config::HeartbeatWatcher.parse argv
24
+ @daemonize_options = {
25
+ :app_name => 'tengine_heartbeat_watcher',
26
+ :ARGV => [@config[:action]],
27
+ :ontop => !@config[:process][:daemon],
28
+ :multiple => true,
29
+ :dir_mode => :normal,
30
+ :dir => File.expand_path(@config[:process][:pid_dir]),
31
+ }
32
+ Tengine::Core::MethodTraceable.disabled = !@config[:verbose]
33
+ rescue Exception
34
+ puts "[#{$!.class.name}] #{$!.message}\n " << $!.backtrace.join("\n ")
35
+ raise
36
+ end
37
+
38
+ def pid
39
+ @pid ||= sprintf "process:%s/%d", ENV["MM_SERVER_NAME"], Process.pid
40
+ end
41
+
42
+ def sender
43
+ @sender ||= Tengine::Event::Sender.new Tengine::Mq::Suite.new(@config[:event_queue])
44
+ end
45
+
46
+ def send_last_event
47
+ sender.fire "finished.process.hbw.tengine", :key => @uuid, :source_name => pid, :sender_name => pid, :occurred_at => Time.now, :level_key => :info, :keep_connection => true
48
+ sender.stop
49
+ end
50
+
51
+ def send_periodic_event
52
+ sender.fire "hbw.heartbeat.tengine", :key => @uuid, :source_name => pid, :sender_name => pid, :occurred_at => Time.now, :level_key => :debug, :keep_connection => true, :retry_count => 0
53
+ end
54
+
55
+ def send_invalidate_event type, e0
56
+ obj = e0.as_document.symbolize_keys
57
+ Tengine.logger.info "Heartbeat expiration detected! for #{e0.event_type_name} of #{e0.source_name}: last seen #{e0.occurred_at} (#{(Time.now - e0.occurred_at).to_f} secs before)"
58
+ obj.delete :_id
59
+ obj.delete :confirmed
60
+ obj.delete :updated_at
61
+ obj.delete :created_at
62
+ obj.delete :lock_version
63
+ obj[:event_type_name] = type
64
+ obj[:level] = Tengine::Event::LEVELS_INV[:error]
65
+ e1 = Tengine::Event.new obj
66
+ sender.fire e1, :keep_connection => true
67
+ end
68
+
69
+ def search_for_invalid_heartbeat
70
+ t = Time.now
71
+ a = @config[:heartbeat].to_hash.each_pair.map do |e, h|
72
+ Tengine::Core::Event.where(
73
+ :event_type_name => "#{e}.heartbeat.tengine",
74
+ :occurred_at.lte => t - h[:expire]
75
+ )
76
+ end
77
+ a.flatten.each_next_tick do |i|
78
+ yield i if i
79
+ end
80
+ end
81
+
82
+ def run(__file__)
83
+ case @config[:action].to_sym
84
+ when :start
85
+ start_daemon(__file__)
86
+ when :stop
87
+ stop_daemon(__file__)
88
+ when :restart
89
+ stop_daemon(__file__)
90
+ start_daemon(__file__)
91
+ end
92
+ end
93
+
94
+ def start_daemon(__file__)
95
+ pdir = File.expand_path @config[:process][:pid_dir]
96
+ fname = File.basename __file__
97
+ cwd = Dir.getwd
98
+ Daemons.run_proc(fname, @daemonize_options) do
99
+ Dir.chdir(cwd) { self.start }
100
+ end
101
+ end
102
+
103
+ def stop_daemon(__file__)
104
+ fname = File.basename __file__
105
+ Daemons.run_proc(fname, @daemonize_options)
106
+ end
107
+
108
+ def shutdown
109
+ EM.run do
110
+ EM.cancel_timer @invalidate if @invalidate
111
+ EM.cancel_timer @periodic if @periodic
112
+ send_last_event
113
+ end
114
+ end
115
+
116
+ def start
117
+ @config.setup_loggers
118
+
119
+ Mongoid.config.from_hash @config[:db]
120
+ Mongoid.config.option :persist_in_safe_mode, :default => true
121
+
122
+ require 'amqp'
123
+ Mongoid.logger = AMQP::Session.logger = Tengine.logger
124
+
125
+ EM.run do
126
+ sender.wait_for_connection do
127
+ @invalidate = EM.add_periodic_timer 1 do # !!! MAGIC NUMBER
128
+ search_for_invalid_heartbeat do |obj|
129
+ type = case obj.event_type_name
130
+ when /job|core|hbw|resourcew|atd/ then
131
+ "expired.#$&.heartbeat.tengine"
132
+ end
133
+ EM.next_tick do
134
+ send_invalidate_event type, obj
135
+ end
136
+ end
137
+ end
138
+ int = @config[:heartbeat][:hbw][:interval].to_i
139
+ if int and int > 0
140
+ @periodic = EM.add_periodic_timer int do
141
+ send_periodic_event
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ extend Tengine::Core::MethodTraceable
149
+ method_trace(*instance_methods(false))
150
+ end
151
+
152
+ #
153
+ # Local Variables:
154
+ # mode: ruby
155
+ # coding: utf-8-unix
156
+ # indent-tabs-mode: nil
157
+ # tab-width: 4
158
+ # ruby-indent-level: 2
159
+ # fill-column: 79
160
+ # default-justification: full
161
+ # End:
@@ -0,0 +1,22 @@
1
+ require 'tengine/core'
2
+
3
+ # see http://www.ruzee.com/blog/2006/11/redirecting-stdout-to-logger-with-ruby-on-rails
4
+ class Tengine::Core::IoToLogger
5
+ def initialize(logger, method_to_write = :info)
6
+ @logger = logger
7
+ @method_to_write = method_to_write
8
+ end
9
+ def puts(str)
10
+ @logger.send(@method_to_write, str.strip)
11
+ end
12
+ def write(str)
13
+ @logger.send(@method_to_write, str.strip)
14
+ end
15
+ alias_method :<<, :puts
16
+
17
+ def flush; end # ignore
18
+
19
+ alias_method :to_s, :inspect
20
+
21
+
22
+ end
@@ -0,0 +1,510 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'tengine/core'
3
+
4
+ require 'tengine/event'
5
+ require 'tengine/mq'
6
+ require 'eventmachine'
7
+ require 'selectable_attr'
8
+
9
+ class Tengine::Core::Kernel
10
+ include ::SelectableAttr::Base
11
+ include Tengine::Core::KernelRuntime
12
+ include Tengine::Core::EventExceptionReportable
13
+
14
+ attr_reader :config, :status
15
+ attr_accessor :before_delegate, :after_delegate
16
+
17
+ def initialize(config)
18
+ @status = :initialized
19
+ @config = config
20
+ @processing_event = false
21
+ end
22
+
23
+ def start
24
+ if block_given?
25
+ block = Proc.new
26
+ else
27
+ block = Proc.new do
28
+ self.stop
29
+ end
30
+ end
31
+ update_status(:starting)
32
+ bind
33
+ if config[:tengined][:wait_activation]
34
+ update_status(:waiting_activation)
35
+ wait_for_activation(&block)
36
+ else
37
+ activate(&block)
38
+ end
39
+ end
40
+
41
+ def stop(force = false)
42
+ if self.status == :running
43
+ update_status(:shutting_down)
44
+ mq.initiate_termination do
45
+ mq.unsubscribe do
46
+ EM.cancel_timer(@heartbeat_timer) if @heartbeat_timer
47
+ send_last_event do
48
+ close_if_shutting_down do
49
+ update_status(:terminated)
50
+ EM.stop
51
+ yield if block_given?
52
+ end
53
+ end
54
+ end
55
+ end
56
+ else
57
+ update_status(:shutting_down)
58
+ # wait_for_actiontion中の処理を停止させる必要がある
59
+ end
60
+ end
61
+
62
+ def self.top
63
+ @top ||= eval("self", TOPLEVEL_BINDING)
64
+ end
65
+
66
+ def dsl_context
67
+ unless @dsl_context
68
+ top = self.class.top
69
+ top.singleton_class.module_eval do
70
+ include Tengine::Core::DslLoader
71
+ end
72
+ top.__kernel__ = self
73
+ top.config = config
74
+ @dsl_context = top
75
+ end
76
+ @dsl_context
77
+ end
78
+ alias_method :context, :dsl_context
79
+
80
+ def evaluate
81
+ dsl_context.__evaluate__
82
+ end
83
+
84
+
85
+ def bind
86
+ # dsl_context.__evaluate__
87
+ # Tengine::Core::stdout_logger.debug("Hanlder bindings:\n" << dsl_context.to_a.inspect)
88
+ # Tengine::Core::HandlerPath.default_driver_version = config.dsl_version
89
+ Tengine::Core::HandlerPath.default_driver_version = config.dsl_version
90
+ end
91
+
92
+ def wait_for_activation(&block)
93
+ activated = false
94
+ activation_file_name = "#{config[:tengined][:activation_dir]}\/tengined_#{Process.pid}.activation"
95
+ start_time = Time.now
96
+ while((Time.now - start_time).to_i <= config[:tengined][:activation_timeout].to_i) do
97
+ if File.exist?(activation_file_name)
98
+ # ファイルが見つかった
99
+ activated = true
100
+ break
101
+ end
102
+ sleep 1
103
+ end
104
+ if activated
105
+ File.delete(activation_file_name)
106
+ # activate開始
107
+ activate(&block)
108
+ else
109
+ update_status(:shutting_down)
110
+ raise Tengine::Core::ActivationTimeoutError, "activation file found timeout error."
111
+ end
112
+ end
113
+
114
+ def activate
115
+ EM.run do
116
+ setup_mq_connection
117
+ subscribe_queue do
118
+ enable_heartbeat
119
+ yield(mq) if block_given? # このyieldは接続テストのための処理をTengine::Core:Bootstrapが定義するのに使われます。
120
+ em_setup_blocks.each{|block| block.call }
121
+ end
122
+ end
123
+ end
124
+
125
+ # subscribe to messages in the queue
126
+ def subscribe_queue
127
+ confirm = proc do |*|
128
+ # queueへの接続までできたら稼働中
129
+ # self.status_key = :running if mq.queue
130
+ update_status(:running)
131
+ yield if block_given?
132
+ end
133
+ mq.subscribe(:ack => true, :nowait => false, :confirm => confirm) do |headers, msg|
134
+ process_message(headers, msg)
135
+ end
136
+ end
137
+
138
+ # @return [true] メッセージはイベントストアに保存された
139
+ # @return [それ以外] メッセージは保存されなかった。
140
+ def process_message(headers, msg)
141
+ safety_processing_event(headers) do
142
+ raw_event = parse_event(msg)
143
+ if raw_event.nil?
144
+ headers.ack
145
+ return false
146
+ end
147
+ if raw_event.key.blank?
148
+ Tengine.logger.warn("invalid event which has blank key: #{raw_event.inspect}")
149
+ headers.ack
150
+ return
151
+ end
152
+
153
+ delay = ((ENV['TENGINED_EVENT_DEBUG_DELAY'] || '0').to_f || 0.0)
154
+ sleep delay
155
+
156
+ begin
157
+ # ハートビートは *保存より前に* 特別扱いが必要
158
+ event = case raw_event.event_type_name
159
+ when /finished\.process\.([^.]+)\.tengine$/
160
+ save_heartbeat_ok(raw_event)
161
+ when /expired\.([^.]+)\.heartbeat\.tengine$/
162
+ save_heartbeat_ng(raw_event)
163
+ when /heartbeat\.tengine$/ # when の順番に注意
164
+ save_heartbeat_beat(raw_event)
165
+ when /(alert|stop)\.execution\.job\.tengine$/
166
+ save_scheduling_event(raw_event)
167
+ when /\.failed\.tengined$/
168
+ save_failed_event(raw_event)
169
+ else
170
+ save_event(raw_event)
171
+ end
172
+
173
+ rescue Mongo::OperationFailure, Mongoid::Errors::Validations => e
174
+ Tengine.logger.warn("failed to store an event.\n[#{e.class.name}] #{e.message}")
175
+ # Model.exists?だと上手くいかない時があるのでModel.whereを使っています
176
+ # fire_failed_event(raw_event) if Tengine::Core::Event.exists?(confitions: { key: raw_event.key, sender_name: raw_event.sender_name })
177
+ fire_failed_event(raw_event) if Tengine::Core::Event.where(:key => raw_event.key, :sender_name => raw_event.sender_name).count > 0
178
+ headers.ack
179
+ return false
180
+
181
+ rescue Exception => e
182
+ Tengine.logger.error("failed to save an event #{raw_event.inspect}\n[#{e.class.name}] #{e.message}")
183
+ headers.ack
184
+ return false
185
+ end
186
+
187
+ unless event
188
+ headers.ack
189
+ return false
190
+ end
191
+ event.kernel = self
192
+
193
+ ack_policy = ack_policy_for(event)
194
+ safety_processing_headers(headers, event, ack_policy) do
195
+ ack if ack_policy == :at_first
196
+ handlers = find_handlers(event)
197
+ safty_handlers(handlers) do
198
+ delegate(event, handlers)
199
+ headers.ack if all_submitted?
200
+ end
201
+ end
202
+ close_if_shutting_down
203
+ true
204
+ end
205
+ end
206
+
207
+ require 'uuid'
208
+ HEARTBEAT_EVENT_TYPE_NAME = "core.heartbeat.tengine".freeze
209
+ HEARTBEAT_ATTRIBUTES = {
210
+ :key => UUID.new.generate,
211
+ :level => Tengine::Event::LEVELS_INV[:debug],
212
+ :source_name => sprintf("process:%s/%d", ENV["MM_SERVER_NAME"], Process.pid),
213
+ :sender_name => sprintf("process:%s/%d", ENV["MM_SERVER_NAME"], Process.pid),
214
+ :retry_count => 0,
215
+ }.freeze
216
+
217
+ def enable_heartbeat
218
+ n = config[:heartbeat][:core][:interval].to_i
219
+ if n and n > 0
220
+ EM.defer do
221
+ @heartbeat_timer = EM.add_periodic_timer(n) do
222
+ Tengine::Core.stdout_logger.debug("sending heartbeat") if config[:verbose]
223
+ sender.fire(HEARTBEAT_EVENT_TYPE_NAME, HEARTBEAT_ATTRIBUTES.dup)
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ def fire(*args, &block)
230
+ sender.fire(*args, &block)
231
+ end
232
+
233
+ def sender
234
+ unless @sender
235
+ @sender = Tengine::Event::Sender.new(mq)
236
+ @sender.default_keep_connection = true
237
+ end
238
+ @sender
239
+ end
240
+
241
+ def mq
242
+ @mq ||= Tengine::Mq::Suite.new(config[:event_queue])
243
+ end
244
+
245
+ private
246
+
247
+ def setup_mq_connection
248
+ mq.add_hook :'connection.on_tcp_connection_failure' do |set|
249
+ case @status when :terminated, :shutting_down then
250
+ raise "Could not properly shut down; MQ broker is missing."
251
+ end
252
+ end
253
+
254
+ # see http://rdoc.info/github/ruby-amqp/amqp/master/file/docs/ErrorHandling.textile#Recovering_from_network_connection_failures
255
+ # mq.connection raiases AMQP::TCPConnectionFailed unless connects to MQ.
256
+ mq.add_hook :'connection.on_error' do |conn, connection_close|
257
+ Tengine::Core.stderr_logger.error("mq.connection.on_error connection_close: " << connection_close.inspect)
258
+ end
259
+ mq.add_hook :'connection.on_tcp_connection_loss' do |conn, settings|
260
+ Tengine::Core.stderr_logger.warn("mq.connection.on_tcp_connection_loss.")
261
+ end
262
+ mq.add_hook :'connection.after_recovery' do |session, settings|
263
+ Tengine::Core.stderr_logger.info("mq.connection.after_recovery: recovered successfully.")
264
+ end
265
+ # on_open, on_closedに渡されたブロックは、何度再接続をしても最初の一度だけしか呼び出されないが、
266
+ # after_recovery(on_recovery)に渡されたブロックは、再接続の度に呼び出されます。
267
+ # connection.on_open{ Tengine::Core.stderr_logger.info "mq.connection.on_open first time" }
268
+ # connection.on_closed{ Tengine::Core.stderr_logger.info "mq.connection.on_closed first time" }
269
+
270
+ mq.add_hook :'channel.on_error' do |ch, channel_close|
271
+ Tengine::Core.stderr_logger.error("mq.channel.on_error channel_close: " << channel_close.inspect)
272
+ # raise channel_close.reply_text
273
+ # channel_close.reuse # channel.on_error時にどのように振る舞うべき?
274
+ end
275
+ end
276
+
277
+ def parse_event(msg)
278
+ raw_event = Tengine::Event.parse(msg)
279
+ Tengine.logger.debug("received an event #{raw_event.inspect}")
280
+ return raw_event
281
+ rescue Exception => e
282
+ Tengine.logger.error("failed to parse a message because of [#{e.class.name}] #{e.message}.\n#{msg}")
283
+ return nil
284
+ end
285
+
286
+ def fire_failed_event(raw_event)
287
+ EM.next_tick do
288
+ # failedということはraw_eventはぶっこわれている。あらゆる仮定は無意味だ。
289
+ event_attributes = {
290
+ :level => Tengine::Event::LEVELS_INV[:error],
291
+ :properties => { :original_event => raw_event }
292
+ }
293
+ case etn = raw_event.event_type_name
294
+ when Tengine::Core::Event::EVENT_TYPE_NAME.format
295
+ Tengine.logger.debug("sending #{raw_event.event_type_name}.failed.tengined event.") if config[:verbose]
296
+ sender.fire("#{raw_event.event_type_name}.failed.tengined", event_attributes)
297
+ else
298
+ Tengine.logger.debug("sending failed.tengined event.") if config[:verbose]
299
+ sender.fire("failed.tengined", event_attributes)
300
+ end
301
+ end
302
+ end
303
+
304
+ def save_failed_event(raw_event)
305
+ # これに失敗したときにさらに failed_event を fire してしまうと無限
306
+ # に fire が続いてしまうので NG.
307
+ event = Tengine::Core::Event.create!(
308
+ raw_event.attributes.update(:confirmed => (raw_event.level.to_i <= config.confirmation_threshold)))
309
+ Tengine.logger.debug("saved an event #{event.inspect}")
310
+ event
311
+ rescue Mongo::OperationFailure => e
312
+ Tengine.logger.error("failed to save an event #{raw_event.inspect}\n[#{e.class.name}] #{e.message}")
313
+ # FIXME!!
314
+ # このままではログに埋もれてしまうのでなんとかすべき。
315
+ # 案1 : root@にメールを投げる
316
+ # 案2 : プロセスが死ぬ
317
+ # 案3 : ...
318
+ return nil
319
+ end
320
+
321
+ # 受信したイベントを登録
322
+ def save_event(raw_event)
323
+ event = Tengine::Core::Event.create!(
324
+ raw_event.attributes.update(:confirmed => (raw_event.level.to_i <= config.confirmation_threshold)))
325
+ Tengine.logger.debug("saved an event #{event.inspect}")
326
+ event
327
+ end
328
+
329
+ def save_scheduling_event(raw_event)
330
+ cond = {
331
+ :event_type_name => raw_event.event_type_name,
332
+ :source_name => raw_event.source_name,
333
+ }
334
+ event = Tengine::Core::Event.find_or_create_then_update_with_block cond do |event|
335
+ if event.new_record?
336
+ event.write_attributes raw_event.attributes
337
+ event.confirmed = (raw_event.level.to_i <= config.confirmation_threshold)
338
+ else
339
+ nil
340
+ end
341
+ end
342
+ case event
343
+ when FalseClass
344
+ Tengine.logger.error("failed to save event (after several retries). #{raw_event.inspect}")
345
+ when NilClass
346
+ Tengine.logger.debug("this event is duplicated, ignoring now. #{raw_event.inspect}")
347
+ else
348
+ Tengine.logger.debug("saved an event #{event.inspect}")
349
+ end
350
+ return event
351
+ end
352
+
353
+ def save_heartbeat_beat(raw_event)
354
+ event = Tengine::Core::Event.find_or_create_then_update_with_block :key => raw_event.key do |event|
355
+ # beatを保存していいのは、
356
+ # * 以前にひとつも登録がないとき
357
+ # * もうbeatが保存されているとき
358
+ # beatを保存してはいけないのは、
359
+ # * もうokが保存されているとき
360
+ # * もうngが保存されているとき
361
+ if event.new_record? or event.event_type_name == raw_event.event_type_name
362
+ event.write_attributes raw_event.attributes.update(:confirmed => (raw_event.level.to_i <= config.confirmation_threshold))
363
+ else
364
+ nil
365
+ end
366
+ end
367
+ case event
368
+ when FalseClass
369
+ Tengine.logger.error("failed to save event (after several retries). #{raw_event.inspect}")
370
+ when NilClass
371
+ Tengine.logger.debug("this event is duplicated, ignoring now. #{raw_event.inspect}")
372
+ else
373
+ Tengine.logger.debug("saved an event #{event.inspect}")
374
+ end
375
+ return event
376
+ end
377
+
378
+ def save_heartbeat_ng(raw_event)
379
+ event = Tengine::Core::Event.find_or_create_then_update_with_block :key => raw_event.key do |event|
380
+ # ngを保存していいのは、
381
+ # * 以前にひとつも登録がないとき
382
+ # * もうbeatが保存されているとき
383
+ # * もうokが保存されているとき
384
+ # ngを保存してはいけないのは、
385
+ # * もうngが保存されているとき
386
+ if event.new_record? or event.event_type_name != raw_event.event_type_name
387
+ event.write_attributes raw_event.attributes.update(:confirmed => (raw_event.level.to_i <= config.confirmation_threshold))
388
+ else
389
+ nil
390
+ end
391
+ end
392
+ case event
393
+ when FalseClass
394
+ Tengine.logger.error("failed to save event (after several retries). #{raw_event.inspect}")
395
+ when NilClass
396
+ Tengine.logger.debug("this event is duplicated, ignoring now. #{raw_event.inspect}")
397
+ else
398
+ Tengine.logger.debug("saved an event #{event.inspect}")
399
+ end
400
+ return event
401
+ end
402
+
403
+ def save_heartbeat_ok(raw_event)
404
+ event = Tengine::Core::Event.find_or_create_then_update_with_block :key => raw_event.key do |event|
405
+ # okを保存していいのは、
406
+ # * 以前にひとつも登録がないとき
407
+ # * もうbeatが保存されているとき
408
+ # okを保存してはいけないのは、
409
+ # * もうokが保存されているとき
410
+ # * もうngが保存されているとき
411
+ beat_type_name = raw_event.event_type_name.sub(/^finished\.process\.(.+?)\.tengine$/, "\\1.heartbeat.tengine")
412
+ if event.new_record? or event.event_type_name == beat_type_name
413
+ event.write_attributes raw_event.attributes.update(:confirmed => (raw_event.level.to_i <= config.confirmation_threshold))
414
+ else
415
+ nil
416
+ end
417
+ end
418
+ case event
419
+ when FalseClass
420
+ Tengine.logger.error("failed to save event (after several retries). #{raw_event.inspect}")
421
+ when NilClass
422
+ Tengine.logger.debug("this event is duplicated, ignoring now. #{raw_event.inspect}")
423
+ else
424
+ Tengine.logger.debug("saved an event #{event.inspect}")
425
+ end
426
+ return event
427
+ end
428
+
429
+ # イベントハンドラの取得
430
+ def find_handlers(event)
431
+ handlers = Tengine::Core::HandlerPath.find_handlers(event.event_type_name)
432
+ Tengine.logger.debug("handlers found for #{event.event_type_name.inspect}: " << handlers.map{|h| "#{h.driver.name} #{h.id.to_s}"}.join(", "))
433
+ handlers
434
+ end
435
+
436
+ def delegate(event, handlers)
437
+ before_delegate.call if before_delegate.respond_to?(:call)
438
+ handlers.each do |handler|
439
+ safety_handler(handler) do
440
+ # block = dsl_context.__block_for__(handler)
441
+ report_on_exception(dsl_context, event) do
442
+ # handler.process_event(event, &block)
443
+ if handler.match?(event)
444
+ handler.process_event(event)
445
+ end
446
+ end
447
+ end
448
+ end
449
+ after_delegate.call if after_delegate.respond_to?(:call)
450
+ ActiveSupport::Dependencies.clear unless config.tengined.cache_drivers
451
+ end
452
+
453
+ def close_if_shutting_down
454
+ # unsubscribed されている場合は安全な停止を行う
455
+ # return if mq.queue.default_consumer
456
+ case status when :shutting_down, :terminated then
457
+ Tengine::Core.stdout_logger.warn("connection closing...")
458
+ mq.stop do
459
+ yield if block_given?
460
+ EM.stop
461
+ end
462
+ end
463
+ end
464
+
465
+ STATUS_LIST = [
466
+ :initialized, # 初期化済み
467
+ :starting, # 起動中
468
+ :waiting_activation, # 稼働要求待ち
469
+ :running, # 稼働中
470
+ :shutting_down, # 停止中
471
+ :terminated, # 停止済
472
+ ].freeze
473
+
474
+ # TODO 状態遷移図、状態遷移表に基づいたチェックを入れるべき
475
+ # https://cacoo.com/diagrams/hwYJGxDuumYsmFzP#EBF87
476
+ def update_status(status)
477
+ Tengine::Core.stdout_logger.info("#{self.class.name}#update_status #{@status.inspect} ==================> #{status.inspect}")
478
+ raise ArgumentError, "Unkown status #{status.inspect}" unless STATUS_LIST.include?(status)
479
+ @status_filepath ||= File.expand_path("tengined_#{Process.pid}.status", config.status_dir)
480
+ @status = status
481
+ File.open(@status_filepath, "w"){|f| f.write(status.to_s)}
482
+ rescue Exception => e
483
+ Tengine::Core.stderr_logger.error("#{self.class.name}#update_status failure. [#{e.class.name}] #{e.message}\n " << e.backtrace.join("\n "))
484
+ raise e
485
+ end
486
+
487
+ def send_last_event
488
+ argh = HEARTBEAT_ATTRIBUTES.dup
489
+ argh[:level] = Tengine::Event::LEVELS_INV[:info]
490
+ argh.delete :retry_count # use default
491
+ sender.fire "finished.process.core.tengine", argh do
492
+ # 他のデーモンと違ってfinishedをfireしたからといってsender.stopし
493
+ # てよいとは限らない(裏でまだイベント処理中かも)
494
+ yield
495
+ end
496
+ end
497
+
498
+ # 自動でログ出力する
499
+ extend Tengine::Core::MethodTraceable
500
+ method_trace(:start, :stop, :bind, :wait_for_activation, :activate,
501
+ :setup_mq_connection, :subscribe_queue, # :update_status, # update_statusは別途ログ出力します
502
+ :process_message, :parse_event, :fire_failed_event, :save_event,
503
+ :find_handlers, :delegate, :close_if_shutting_down, :enable_heartbeat
504
+ )
505
+ end
506
+
507
+
508
+ class Tengine::Core::ActivationTimeoutError < StandardError
509
+ end
510
+