procon_bypass_man 0.3.6 → 0.3.8
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.
- 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
|
}
|