procon_bypass_man 0.3.6 → 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +4 -0
- data/.github/workflows/gitleacks.yml +1 -1
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/ruby.yml +2 -2
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -2
- data/README.md +2 -2
- data/bin/validate_external_input +19 -0
- data/docs/getting_started.md +28 -0
- data/docs/setting/integration_external_input_serial_port.md +56 -0
- data/docs/setting/integration_external_input_serial_port_format.md +36 -0
- data/lib/procon_bypass_man/background/jobs/report_procon_performance_measurements_job.rb +1 -0
- data/lib/procon_bypass_man/bypass/procon_to_switch.rb +16 -1
- data/lib/procon_bypass_man/commands/print_boot_message_command.rb +4 -1
- data/lib/procon_bypass_man/configuration.rb +31 -14
- data/lib/procon_bypass_man/external_input/boot_message.rb +21 -0
- data/lib/procon_bypass_man/external_input/channels/base.rb +22 -0
- data/lib/procon_bypass_man/external_input/channels/serial_port_channel.rb +56 -0
- data/lib/procon_bypass_man/external_input/channels/tcpip_channel.rb +131 -0
- data/lib/procon_bypass_man/external_input/channels.rb +12 -0
- data/lib/procon_bypass_man/external_input/external_data.rb +79 -0
- data/lib/procon_bypass_man/external_input.rb +31 -0
- data/lib/procon_bypass_man/processor.rb +3 -2
- data/lib/procon_bypass_man/procon/button_collection.rb +5 -0
- data/lib/procon_bypass_man/procon/performance_measurement/measurements_summarizer.rb +4 -0
- data/lib/procon_bypass_man/procon/performance_measurement.rb +7 -1
- data/lib/procon_bypass_man/procon.rb +17 -2
- data/lib/procon_bypass_man/procon_display/status.rb +0 -2
- data/lib/procon_bypass_man/support/forever.rb +51 -0
- data/lib/procon_bypass_man/support/proccess_cheacker.rb +14 -0
- data/lib/procon_bypass_man/support/retryable.rb +5 -2
- data/lib/procon_bypass_man/support/simple_tcp_server.rb +63 -0
- data/lib/procon_bypass_man/support/watchdog.rb +23 -0
- data/lib/procon_bypass_man/support/web_connectivity_checker.rb +41 -0
- data/lib/procon_bypass_man/version.rb +1 -1
- data/lib/procon_bypass_man/websocket/client.rb +11 -9
- data/lib/procon_bypass_man.rb +16 -3
- data/project_template/app.rb +11 -1
- data/project_template/app.rb.erb +11 -1
- data/sig/main.rbs +2 -2
- metadata +17 -4
- data/lib/procon_bypass_man/websocket/forever.rb +0 -47
- data/lib/procon_bypass_man/websocket/watchdog.rb +0 -19
@@ -0,0 +1,131 @@
|
|
1
|
+
module ProconBypassMan
|
2
|
+
module ExternalInput
|
3
|
+
module Channels
|
4
|
+
class TCPIPChannel < Base
|
5
|
+
class ShutdownSignal < StandardError; end
|
6
|
+
|
7
|
+
class AppServer < SimpleTCPServer
|
8
|
+
@command_queue = Queue.new
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :command_queue
|
12
|
+
end
|
13
|
+
|
14
|
+
def post_init
|
15
|
+
ProconBypassMan.logger.info { "[ExternalInput][TCPIPChannel] A client has connected" }
|
16
|
+
end
|
17
|
+
|
18
|
+
def unbind
|
19
|
+
ProconBypassMan.logger.info { "[ExternalInput][TCPIPChannel] A client has disconnected" }
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def receive_data(client, data)
|
24
|
+
case data
|
25
|
+
when /^{/
|
26
|
+
self.class.command_queue.push(data)
|
27
|
+
client.write("OK\n")
|
28
|
+
return
|
29
|
+
when /^\n/
|
30
|
+
if self.class.command_queue.empty?
|
31
|
+
client.write("EMPTY\n")
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
data = self.class.command_queue.pop
|
36
|
+
client.write("#{data}\n")
|
37
|
+
return
|
38
|
+
else
|
39
|
+
client.write("Unknown command\n")
|
40
|
+
return
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(port: )
|
46
|
+
@port = port
|
47
|
+
super()
|
48
|
+
|
49
|
+
begin
|
50
|
+
@server = AppServer.new('0.0.0.0', @port)
|
51
|
+
rescue Errno::EADDRINUSE # NOTE: Address already in use - bind(2) for "0.0.0.0" port XXXX
|
52
|
+
ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] 起動に失敗しました。#{e.message}")
|
53
|
+
@server_thread = Thread.start {}
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
# NOTE: masterプロセスで起動する
|
58
|
+
@server_thread = Thread.start do
|
59
|
+
begin
|
60
|
+
loop do
|
61
|
+
@server.start_server
|
62
|
+
@server.run
|
63
|
+
end
|
64
|
+
rescue Errno::EPIPE, EOFError, Errno::ECONNRESET => e
|
65
|
+
ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] #{e.message}(#{e})")
|
66
|
+
sleep(5)
|
67
|
+
|
68
|
+
@server.shutdown
|
69
|
+
retry
|
70
|
+
rescue ShutdownSignal => e
|
71
|
+
ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] ShutdownSignalを受け取りました。終了します。")
|
72
|
+
@server.shutdown
|
73
|
+
rescue => e
|
74
|
+
ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] #{e.message}(#{e})")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# NOTE: bypassプロセスから呼ばれ、masterプロセスへ繋ぐ
|
80
|
+
# @return [String, NilClass]
|
81
|
+
def read
|
82
|
+
@socket ||= TCPSocket.new('0.0.0.0', @port)
|
83
|
+
read_command = "\n"
|
84
|
+
@socket.write(read_command)
|
85
|
+
response = @socket.gets&.strip
|
86
|
+
# ProconBypassMan.logger.debug { "Received: #{response}" }
|
87
|
+
|
88
|
+
case response
|
89
|
+
when /^{/
|
90
|
+
return response
|
91
|
+
when /^EMPTY/, ''
|
92
|
+
return nil
|
93
|
+
else
|
94
|
+
ProconBypassMan.logger.warn { "[ExternalInput][TCPIPChannel] Unknown response(#{response}, codepoints: #{response.codepoints})" }
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
rescue Errno::EPIPE, EOFError => e
|
98
|
+
@socket = nil
|
99
|
+
sleep(10)
|
100
|
+
ProconBypassMan.logger.error { "[ExternalInput][TCPIPChannel] #{e.message}!!!!!!!(#{e})" }
|
101
|
+
retry
|
102
|
+
rescue => e
|
103
|
+
@socket = nil
|
104
|
+
ProconBypassMan.logger.error { "[ExternalInput][TCPIPChannel] #{e.message} が起きました(#{e})" }
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def shutdown
|
109
|
+
ProconBypassMan.logger.info { "[ExternalInput][TCPIPChannel] shutdown" }
|
110
|
+
@server_thread.raise(ShutdownSignal)
|
111
|
+
end
|
112
|
+
|
113
|
+
def alive_server?
|
114
|
+
return false if not @server_thread.alive?
|
115
|
+
|
116
|
+
begin
|
117
|
+
TCPSocket.new('0.0.0.0', @port).close
|
118
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
119
|
+
return false
|
120
|
+
end
|
121
|
+
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
def display_name_for_boot_message
|
126
|
+
"TCPIP(port: #{@port})"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProconBypassMan
|
4
|
+
module ExternalInput
|
5
|
+
module Channels
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require "procon_bypass_man/external_input/channels/base"
|
11
|
+
require "procon_bypass_man/external_input/channels/serial_port_channel"
|
12
|
+
require "procon_bypass_man/external_input/channels/tcpip_channel"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ProconBypassMan
|
2
|
+
module ExternalInput
|
3
|
+
class ExternalData
|
4
|
+
UNPRESS_BUTTONS = Set.new(ProconBypassMan::Procon::ButtonCollection.available.map { |x| "un#{x}".to_sym })
|
5
|
+
|
6
|
+
# @return [String, NilClass] 16進数表現のデータ
|
7
|
+
attr_reader :hex
|
8
|
+
|
9
|
+
# @return [String, NilClass] ログに表示する用
|
10
|
+
attr_reader :raw_data
|
11
|
+
|
12
|
+
# @raise [ParseError]
|
13
|
+
# @return [ExternalData] JSON か カンマ区切りのbuttons
|
14
|
+
def self.parse!(raw_data)
|
15
|
+
raise ParseError unless raw_data.ascii_only?
|
16
|
+
|
17
|
+
if is_json(raw_data)
|
18
|
+
begin
|
19
|
+
json = JSON.parse(raw_data)
|
20
|
+
return new(hex: json['hex'], buttons: json['buttons'])
|
21
|
+
rescue JSON::ParserError
|
22
|
+
raise ParseError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
return new(hex: nil, buttons: raw_data.scan(/:\w+:/).map { |x| x.gsub(':', '') }, raw_data: raw_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [String] raw_data
|
30
|
+
# @return [Boolean]
|
31
|
+
def self.is_json(raw_data)
|
32
|
+
raw_data.start_with?('{')
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [String, NilClass] hex
|
36
|
+
# @param [Array<String>, NilClass] buttons
|
37
|
+
# @param [String, NilClass] raw_data
|
38
|
+
def initialize(hex: , buttons: , raw_data: nil)
|
39
|
+
@hex = hex
|
40
|
+
@buttons = buttons || []
|
41
|
+
@raw_data = raw_data
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String, NilClass]
|
45
|
+
def to_binary
|
46
|
+
return nil if @hex.nil?
|
47
|
+
[@hex].pack('H*')
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Array<Symbol>]
|
51
|
+
def press_buttons
|
52
|
+
buttons.select do |button|
|
53
|
+
ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP[button]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Array<Symbol>]
|
58
|
+
def unpress_buttons
|
59
|
+
buttons.select { |button|
|
60
|
+
UNPRESS_BUTTONS.include?(button)
|
61
|
+
}.map { |b| to_button(b.to_s).to_sym }
|
62
|
+
end
|
63
|
+
|
64
|
+
# NOTE: ログに表示する用
|
65
|
+
# @return [Array<Symbol>]
|
66
|
+
def buttons
|
67
|
+
@buttons.map(&:to_sym)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# @return [String]
|
73
|
+
# NOTE: un#{button} って名前をbuttonに変換する
|
74
|
+
def to_button(button)
|
75
|
+
button.sub(/^un/, '')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProconBypassMan
|
4
|
+
module ExternalInput
|
5
|
+
class ParseError < StandardError; end
|
6
|
+
|
7
|
+
# @return [Array<ProconBypassMan::ExternalInput::Channels::Base>]
|
8
|
+
def self.channels
|
9
|
+
@@channels ||= ProconBypassMan.config.external_input_channels
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.shutdown
|
13
|
+
channels.each(&:shutdown)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [NilClass, String]
|
17
|
+
# NOTE: 外部入力からのreadがボトルネックになるなら、Concurrent::Futureを使ってプロコンからの読み出しと並列化することを検討する
|
18
|
+
def self.read
|
19
|
+
value = nil
|
20
|
+
channels.each do |channel|
|
21
|
+
value = channel.read
|
22
|
+
break if value
|
23
|
+
end
|
24
|
+
value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require "procon_bypass_man/external_input/external_data"
|
30
|
+
require "procon_bypass_man/external_input/channels.rb"
|
31
|
+
require "procon_bypass_man/external_input/boot_message"
|
@@ -5,12 +5,13 @@ class ProconBypassMan::Processor
|
|
5
5
|
@binary = binary
|
6
6
|
end
|
7
7
|
|
8
|
+
# @param [ProconBypassMan::ExternalInput::ExternalData, NilClass] external_input_data
|
8
9
|
# @return [String] 加工後の入力データ
|
9
|
-
def process
|
10
|
+
def process(external_input_data: nil)
|
10
11
|
return @binary.raw unless @binary.user_operation_data?
|
11
12
|
|
12
13
|
procon = ProconBypassMan::Procon.new(@binary.raw)
|
13
14
|
procon.apply!
|
14
|
-
procon.to_binary
|
15
|
+
procon.to_binary(external_input_data: external_input_data)
|
15
16
|
end
|
16
17
|
end
|
@@ -9,6 +9,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
|
|
9
9
|
:time_taken_p95,
|
10
10
|
:time_taken_p99,
|
11
11
|
:time_taken_max,
|
12
|
+
:external_input_time_max,
|
12
13
|
:read_error_count,
|
13
14
|
:write_error_count,
|
14
15
|
:gc_count,
|
@@ -26,6 +27,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
|
|
26
27
|
write_time_max = 0
|
27
28
|
read_time_max = 0
|
28
29
|
time_taken_max = 0
|
30
|
+
external_input_time_max = 0
|
29
31
|
interval_from_previous_succeed_max = 0
|
30
32
|
@spans.each do |span|
|
31
33
|
# NOTE @spans.map(&:write_time).sort.last と同じことだけど、処理コストを軽くするためにループを共通化する
|
@@ -33,6 +35,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
|
|
33
35
|
read_time_max = span.read_time if write_time_max < span.read_time
|
34
36
|
time_taken_max = span.time_taken if span.succeed && time_taken_max < span.time_taken
|
35
37
|
interval_from_previous_succeed_max = span.interval_from_previous_succeed if span.succeed && interval_from_previous_succeed_max < span.interval_from_previous_succeed
|
38
|
+
external_input_time_max = span.external_input_time if span.succeed && external_input_time_max < span.external_input_time
|
36
39
|
end
|
37
40
|
|
38
41
|
# NOTE 今はGCを無効にしており、集計するまでもないのでコメントアウトにする. 今後GCを有効にしたバイパスをするかもしれないので残しておく
|
@@ -79,6 +82,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
|
|
79
82
|
time_taken_p95,
|
80
83
|
time_taken_p99,
|
81
84
|
time_taken_max,
|
85
|
+
external_input_time_max,
|
82
86
|
total_read_error_count,
|
83
87
|
total_write_error_count,
|
84
88
|
gc_count,
|
@@ -12,7 +12,7 @@ require 'procon_bypass_man/procon/performance_measurement/last_bypass_at'
|
|
12
12
|
module ProconBypassMan::Procon::PerformanceMeasurement
|
13
13
|
class PerformanceSpan
|
14
14
|
attr_accessor :time_taken, :succeed, :interval_from_previous_succeed, :gc_count, :gc_time
|
15
|
-
attr_reader :write_error_count, :read_error_count, :write_time, :read_time
|
15
|
+
attr_reader :write_error_count, :read_error_count, :write_time, :read_time, :external_input_time
|
16
16
|
|
17
17
|
def initialize
|
18
18
|
@write_error_count = 0
|
@@ -40,9 +40,15 @@ module ProconBypassMan::Procon::PerformanceMeasurement
|
|
40
40
|
return result
|
41
41
|
end
|
42
42
|
|
43
|
+
# @return [void]
|
43
44
|
def record_read_time(&block)
|
44
45
|
@read_time = Benchmark.realtime { block.call }
|
45
46
|
end
|
47
|
+
|
48
|
+
# @return [void]
|
49
|
+
def record_external_input_time(&block)
|
50
|
+
@external_input_time = Benchmark.realtime { block.call }
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
# 全部送ると負荷になるので適当にまびく
|
@@ -128,7 +128,7 @@ class ProconBypassMan::Procon
|
|
128
128
|
end
|
129
129
|
|
130
130
|
# remote macro or pbm action
|
131
|
-
if
|
131
|
+
if(task = ProconBypassMan::RemoteAction::TaskQueueInProcess.non_blocking_shift)
|
132
132
|
case task.type
|
133
133
|
when ProconBypassMan::RemoteAction::Task::TYPE_MACRO
|
134
134
|
no_op_step = :wait_for_0_3 # マクロの最後に固まって最後の入力をし続けるので、無の状態を最後に注入する
|
@@ -187,12 +187,27 @@ class ProconBypassMan::Procon
|
|
187
187
|
status
|
188
188
|
end
|
189
189
|
|
190
|
+
# @param [ProconBypassMan::ExternalInput::ExternalData, NilClass] external_input_data
|
190
191
|
# @return [String]
|
191
|
-
def to_binary
|
192
|
+
def to_binary(external_input_data: nil)
|
192
193
|
if ongoing_mode.name != :manual
|
193
194
|
return user_operation.binary.raw
|
194
195
|
end
|
195
196
|
|
197
|
+
if external_input_data
|
198
|
+
if(external_input_data_raw_binary = external_input_data.to_binary)
|
199
|
+
self.user_operation.merge(external_input_data_raw_binary)
|
200
|
+
else
|
201
|
+
external_input_data.press_buttons.each do |button|
|
202
|
+
self.user_operation.press_button(button)
|
203
|
+
end
|
204
|
+
external_input_data.unpress_buttons.each do |button|
|
205
|
+
self.user_operation.unpress_button(button)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
return self.user_operation.binary.raw
|
209
|
+
end
|
210
|
+
|
196
211
|
if ongoing_macro.ongoing? && (step = ongoing_macro.next_step)
|
197
212
|
BlueGreenProcess::SharedVariable.extend_run_on_this_process = true
|
198
213
|
ongoing_macro.force_neutral_buttons&.each do |force_neutral_button|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ProconBypassMan
|
2
|
+
class Forever
|
3
|
+
# 動作確認方法
|
4
|
+
# - 10秒ごとにrefreshするのでタイムアウトは起きない
|
5
|
+
# - ProconBypassMan::Forever.run { |watchdog| loop { puts(:hi); sleep(10); watchdog.active! } }
|
6
|
+
# - タイムアウトが起きること
|
7
|
+
# - ProconBypassMan::Forever.run { |watchdog| loop { puts(:hi); sleep(10); } }
|
8
|
+
def self.run(&block)
|
9
|
+
loop do
|
10
|
+
new.run(&block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [void]
|
15
|
+
def run(&block)
|
16
|
+
raise(ArgumentError, "need a block") unless block_given?
|
17
|
+
|
18
|
+
thread, watchdog = work_one(callable: block)
|
19
|
+
wait_and_kill_if_outdated(thread, watchdog)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Proc] callable
|
23
|
+
# @return [Array<Thread, ProconBypassMan::Watchdog>]
|
24
|
+
def work_one(callable: )
|
25
|
+
watchdog = ProconBypassMan::Watchdog.new
|
26
|
+
thread = Thread.start do
|
27
|
+
callable.call(watchdog)
|
28
|
+
rescue => e
|
29
|
+
ProconBypassMan.logger.error("[Forever] #{e.full_message}")
|
30
|
+
end
|
31
|
+
|
32
|
+
return [thread, watchdog]
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [ProconBypassMan::Watchdog] watchdog
|
36
|
+
# @param [Thread] thread
|
37
|
+
# @return [void]
|
38
|
+
def wait_and_kill_if_outdated(thread, watchdog)
|
39
|
+
loop do
|
40
|
+
if watchdog.outdated?
|
41
|
+
watchdog.active!
|
42
|
+
ProconBypassMan.logger.error("watchdog timeout!!")
|
43
|
+
thread.kill
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
sleep(10)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,14 +1,17 @@
|
|
1
1
|
module ProconBypassMan
|
2
2
|
class Retryable
|
3
|
-
def self.retryable(tries: , retried: 0, on_no_retry: [])
|
3
|
+
def self.retryable(tries: , retried: 0, on_no_retry: [], log_label: nil, interval_on_retry: 0)
|
4
4
|
return yield(retried)
|
5
5
|
rescue *on_no_retry
|
6
6
|
raise
|
7
|
-
rescue
|
7
|
+
rescue => e
|
8
8
|
if tries <= retried
|
9
9
|
raise
|
10
10
|
else
|
11
11
|
retried = retried + 1
|
12
|
+
ProconBypassMan.logger.debug "[Retryable]#{log_label && "[#{log_label}]"} #{e}が起きました。retryします。#{retried} / #{tries}"
|
13
|
+
|
14
|
+
sleep(interval_on_retry)
|
12
15
|
retry
|
13
16
|
end
|
14
17
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class SimpleTCPServer
|
4
|
+
def initialize(host, port)
|
5
|
+
@host = host
|
6
|
+
@port = port
|
7
|
+
end
|
8
|
+
|
9
|
+
def start_server
|
10
|
+
@connections = []
|
11
|
+
@server = TCPServer.new(@host, @port)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
loop do
|
16
|
+
timeout = 5 # 5秒のタイムアウト
|
17
|
+
readable, _ = IO.select(@connections + [@server], nil, nil, timeout)
|
18
|
+
next if readable.nil? # timeoutを迎えるとnilになる
|
19
|
+
|
20
|
+
readable.each do |socket|
|
21
|
+
if socket == @server
|
22
|
+
client = @server.accept
|
23
|
+
post_init
|
24
|
+
@connections << client
|
25
|
+
else
|
26
|
+
data = socket.gets
|
27
|
+
if data.nil?
|
28
|
+
@connections.delete(socket)
|
29
|
+
unbind
|
30
|
+
socket.close
|
31
|
+
else
|
32
|
+
receive_data(socket, data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue Errno::EBADF, IOError => e
|
37
|
+
unbind
|
38
|
+
@connections = []
|
39
|
+
@server.close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def shutdown
|
44
|
+
@server.close
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Integer]
|
48
|
+
def connections_size
|
49
|
+
@connections.size
|
50
|
+
end
|
51
|
+
|
52
|
+
def post_init
|
53
|
+
# Override this method
|
54
|
+
end
|
55
|
+
|
56
|
+
def receive_data(socket, data)
|
57
|
+
# Override this method
|
58
|
+
end
|
59
|
+
|
60
|
+
def unbind
|
61
|
+
# Override this method
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ProconBypassMan
|
2
|
+
class Watchdog
|
3
|
+
def initialize(timeout: 100)
|
4
|
+
@timeout = timeout
|
5
|
+
active!
|
6
|
+
end
|
7
|
+
|
8
|
+
# @return [Boolean]
|
9
|
+
def outdated?
|
10
|
+
@time < Time.now
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Time]
|
14
|
+
def time
|
15
|
+
@time
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [void]
|
19
|
+
def active!
|
20
|
+
@time = Time.now + @timeout
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class ProconBypassMan::WebConnectivityChecker
|
2
|
+
# @param [String, NilClass] url
|
3
|
+
# @param [String, NilClass] ws_url
|
4
|
+
def initialize(url, ws_url)
|
5
|
+
@url = url
|
6
|
+
@ws_url = ws_url
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
def to_s
|
11
|
+
if @url.nil?
|
12
|
+
return "DISABLE"
|
13
|
+
end
|
14
|
+
|
15
|
+
if alive?
|
16
|
+
return "ENABLE (#{@url}, #{@ws_url})"
|
17
|
+
else
|
18
|
+
return "UNREACHABLE (#{@url})"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @return [Boolean]
|
25
|
+
def alive?
|
26
|
+
uri = URI.parse(@url)
|
27
|
+
response = nil
|
28
|
+
|
29
|
+
begin
|
30
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
31
|
+
request = Net::HTTP::Head.new(uri)
|
32
|
+
response = http.request(request)
|
33
|
+
end
|
34
|
+
rescue StandardError => e
|
35
|
+
ProconBypassMan.logger.error e
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
|
39
|
+
response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPMovedPermanently)
|
40
|
+
end
|
41
|
+
end
|
@@ -7,11 +7,13 @@ module ProconBypassMan
|
|
7
7
|
return unless ProconBypassMan.config.enable_ws?
|
8
8
|
|
9
9
|
Thread.start do
|
10
|
-
Forever.run
|
10
|
+
ProconBypassMan::Forever.run do |watchdog|
|
11
|
+
run(watchdog: watchdog)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
|
-
def self.run
|
16
|
+
def self.run(watchdog: )
|
15
17
|
EventMachine.run do
|
16
18
|
client = ActionCableClient.new(
|
17
19
|
ProconBypassMan.config.current_ws_server_url, {
|
@@ -20,15 +22,15 @@ module ProconBypassMan
|
|
20
22
|
)
|
21
23
|
|
22
24
|
client.connected {
|
23
|
-
ProconBypassMan.logger.info('
|
25
|
+
ProconBypassMan.logger.info('[WebsocketClient] successfully connected in ProconBypassMan::Websocket::Client')
|
24
26
|
}
|
25
27
|
client.subscribed { |msg|
|
26
|
-
ProconBypassMan.logger.info("
|
28
|
+
ProconBypassMan.logger.info("[WebsocketClient] subscribed(#{msg})")
|
27
29
|
ProconBypassMan::SyncDeviceStatsJob.perform(ProconBypassMan::DeviceStatus.current)
|
28
30
|
}
|
29
31
|
|
30
32
|
client.received do |data|
|
31
|
-
ProconBypassMan.logger.info('
|
33
|
+
ProconBypassMan.logger.info('[WebsocketClient] received!!')
|
32
34
|
ProconBypassMan.logger.info(data)
|
33
35
|
|
34
36
|
dispatch(data: data, client: client)
|
@@ -37,20 +39,20 @@ module ProconBypassMan
|
|
37
39
|
end
|
38
40
|
|
39
41
|
client.disconnected {
|
40
|
-
ProconBypassMan.logger.info('
|
42
|
+
ProconBypassMan.logger.info('[WebsocketClient] disconnected!!')
|
41
43
|
client.reconnect!
|
42
44
|
sleep 2
|
43
45
|
}
|
44
46
|
client.errored { |msg|
|
45
|
-
ProconBypassMan.logger.error("
|
47
|
+
ProconBypassMan.logger.error("[WebsocketClient] errored!!, #{msg}")
|
46
48
|
client.reconnect!
|
47
49
|
sleep 2
|
48
50
|
}
|
49
51
|
client.pinged { |msg|
|
50
|
-
|
52
|
+
watchdog.active!
|
51
53
|
|
52
54
|
ProconBypassMan.cache.fetch key: 'ws_pinged', expires_in: 10 do
|
53
|
-
ProconBypassMan.logger.info('
|
55
|
+
ProconBypassMan.logger.info('[WebsocketClient] pinged!!')
|
54
56
|
ProconBypassMan.logger.info(msg)
|
55
57
|
end
|
56
58
|
}
|