cosmos 4.0.3-java → 4.1.0-java
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/.travis.yml +5 -5
- data/Manifest.txt +11 -1
- data/README.md +3 -2
- data/Rakefile +18 -4
- data/appveyor.yml +19 -0
- data/cosmos.gemspec +12 -3
- data/data/config/cmd_tlm_server.yaml +3 -0
- data/data/crc.txt +63 -60
- data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
- data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
- data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
- data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
- data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
- data/demo/lib/cmd_sequence_exporter.rb +52 -0
- data/demo/lib/example_background_task.rb +1 -0
- data/demo/procedures/replay_test.rb +32 -0
- data/ext/cosmos/ext/structure/structure.c +39 -3
- data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
- data/install/config/tools/launcher/launcher.txt +2 -0
- data/lib/cosmos/config/config_parser.rb +2 -0
- data/lib/cosmos/core_ext/io.rb +89 -60
- data/lib/cosmos/gui/qt.rb +5 -8
- data/lib/cosmos/gui/qt_tool.rb +8 -8
- data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
- data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
- data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
- data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
- data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
- data/lib/cosmos/interfaces/udp_interface.rb +27 -14
- data/lib/cosmos/io/buffered_file.rb +0 -1
- data/lib/cosmos/io/json_drb.rb +134 -214
- data/lib/cosmos/io/json_drb_object.rb +22 -61
- data/lib/cosmos/io/json_drb_rack.rb +79 -0
- data/lib/cosmos/io/json_rpc.rb +27 -0
- data/lib/cosmos/io/udp_sockets.rb +102 -58
- data/lib/cosmos/packets/commands.rb +1 -1
- data/lib/cosmos/packets/structure.rb +1 -1
- data/lib/cosmos/packets/structure_item.rb +37 -5
- data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
- data/lib/cosmos/script/replay.rb +60 -0
- data/lib/cosmos/script/script.rb +20 -2
- data/lib/cosmos/script/scripting.rb +9 -9
- data/lib/cosmos/script/tools.rb +14 -0
- data/lib/cosmos/system/system.rb +185 -92
- data/lib/cosmos/system/target.rb +1 -1
- data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
- data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
- data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
- data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
- data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
- data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
- data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
- data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
- data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
- data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
- data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
- data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
- data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
- data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
- data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
- data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
- data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
- data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
- data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
- data/lib/cosmos/tools/replay/replay.rb +5 -505
- data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
- data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
- data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
- data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
- data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
- data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
- data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
- data/lib/cosmos/top_level.rb +34 -24
- data/lib/cosmos/utilities/csv.rb +60 -8
- data/lib/cosmos/version.rb +5 -5
- data/spec/config/config_parser_spec.rb +10 -1
- data/spec/core_ext/socket_spec.rb +4 -2
- data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
- data/spec/install/config/data/data.txt +1 -0
- data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
- data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
- data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
- data/spec/interfaces/serial_interface_spec.rb +1 -1
- data/spec/interfaces/udp_interface_spec.rb +14 -0
- data/spec/io/buffered_file_spec.rb +37 -0
- data/spec/io/json_drb_object_spec.rb +2 -15
- data/spec/io/json_drb_spec.rb +61 -121
- data/spec/io/udp_sockets_spec.rb +42 -2
- data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
- data/spec/packets/binary_accessor_spec.rb +1 -1
- data/spec/packets/packet_item_spec.rb +1 -1
- data/spec/packets/structure_item_spec.rb +5 -6
- data/spec/script/cmd_tlm_server_spec.rb +39 -4
- data/spec/script/commands_disconnect_spec.rb +1 -1
- data/spec/script/commands_spec.rb +2 -1
- data/spec/script/scripting_spec.rb +18 -3
- data/spec/script/telemetry_spec.rb +5 -0
- data/spec/spec_helper.rb +43 -26
- data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
- data/spec/system/system_spec.rb +11 -9
- data/spec/system/target_spec.rb +3 -0
- data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
- data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
- data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
- data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
- data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
- data/spec/tools/launcher/launcher_config_spec.rb +1 -1
- data/spec/tools/table_manager/table_item_spec.rb +1 -1
- data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
- data/spec/top_level/top_level_spec.rb +151 -3
- data/spec/utilities/csv_spec.rb +24 -5
- metadata +61 -9
- data/lib/cosmos/tools/replay/replay_server.rb +0 -91
data/lib/cosmos/gui/qt.rb
CHANGED
|
@@ -252,6 +252,7 @@ module Cosmos
|
|
|
252
252
|
end
|
|
253
253
|
end
|
|
254
254
|
end
|
|
255
|
+
return config_change_success, change_error
|
|
255
256
|
end
|
|
256
257
|
end
|
|
257
258
|
|
|
@@ -458,17 +459,13 @@ class Qt::PlainTextEdit
|
|
|
458
459
|
text << "\n"
|
|
459
460
|
end
|
|
460
461
|
if text =~ /<G>/ or color == Cosmos::GREEN
|
|
461
|
-
text.gsub
|
|
462
|
-
addText(text, Cosmos::GREEN)
|
|
462
|
+
addText(text.gsub(/<G>/, BLANK), Cosmos::GREEN)
|
|
463
463
|
elsif text =~ /<Y>/ or color == Cosmos::YELLOW
|
|
464
|
-
text.gsub
|
|
465
|
-
addText(text, Cosmos::YELLOW)
|
|
464
|
+
addText(text.gsub(/<Y>/, BLANK), Cosmos::YELLOW)
|
|
466
465
|
elsif text =~ /<R>/ or color == Cosmos::RED
|
|
467
|
-
text.gsub
|
|
468
|
-
addText(text, Cosmos::RED)
|
|
466
|
+
addText(text.gsub(/<R>/, BLANK), Cosmos::RED)
|
|
469
467
|
elsif text =~ /<B>/ or color == Cosmos::BLUE
|
|
470
|
-
text.gsub
|
|
471
|
-
addText(text, Cosmos::BLUE)
|
|
468
|
+
addText(text.gsub(/<B>/, BLANK), Cosmos::BLUE)
|
|
472
469
|
else
|
|
473
470
|
addText(text) # default is Cosmos::BLACK
|
|
474
471
|
end
|
data/lib/cosmos/gui/qt_tool.rb
CHANGED
|
@@ -75,21 +75,21 @@ module Cosmos
|
|
|
75
75
|
# @param filename [String] Path to a configuration file
|
|
76
76
|
# @param type [String] File extension, e.g. '.txt'
|
|
77
77
|
# @param tool_name [String] Name of the tool calling this method
|
|
78
|
-
# @return [String|nil] Path to a configuration file
|
|
78
|
+
# @return [String|nil] Path to a configuration file. If a filename was
|
|
79
|
+
# passed and the configuration file is not found, the original filename
|
|
80
|
+
# is returned. If no filename was passed nil is returned.
|
|
79
81
|
def config_path(filename, type, tool_name)
|
|
80
82
|
return filename if filename && File.exist?(filename)
|
|
81
83
|
if filename
|
|
82
84
|
# Add the configuration dir onto the filename
|
|
83
|
-
|
|
85
|
+
new_filename = File.join(@options.config_dir, filename)
|
|
86
|
+
return new_filename if File.exist?(new_filename)
|
|
84
87
|
else
|
|
85
88
|
# No file passed so default to a file named after the class
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if File.exist? filename
|
|
89
|
-
filename
|
|
90
|
-
else
|
|
91
|
-
nil
|
|
89
|
+
new_filename = File.join(@options.config_dir, "#{tool_name}#{type}")
|
|
90
|
+
return new_filename if File.exist?(new_filename)
|
|
92
91
|
end
|
|
92
|
+
filename
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
# Create the exit_action and the about_action. The exit_action is not
|
|
@@ -403,37 +403,37 @@ module Cosmos
|
|
|
403
403
|
end
|
|
404
404
|
|
|
405
405
|
def create_add_breakpoint_action(point)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
406
|
+
action = Qt::Action.new(tr("Add Breakpoint"), self)
|
|
407
|
+
action.statusTip = tr("Add a breakpoint at this line")
|
|
408
|
+
action.connect(SIGNAL('triggered()')) do
|
|
409
409
|
line_at_point(point) do |line|
|
|
410
410
|
add_breakpoint(line)
|
|
411
411
|
emit breakpoint_set(line)
|
|
412
412
|
end
|
|
413
413
|
end
|
|
414
|
-
|
|
414
|
+
action
|
|
415
415
|
end
|
|
416
416
|
|
|
417
417
|
def create_clear_breakpoint_action(point)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
418
|
+
action = Qt::Action.new(tr("Clear Breakpoint"), self)
|
|
419
|
+
action.statusTip = tr("Clear an existing breakpoint at this line")
|
|
420
|
+
action.connect(SIGNAL('triggered()')) do
|
|
421
421
|
line_at_point(point) do |line|
|
|
422
422
|
clear_breakpoint(line)
|
|
423
423
|
emit breakpoint_cleared(line)
|
|
424
424
|
end
|
|
425
425
|
end
|
|
426
|
-
|
|
426
|
+
action
|
|
427
427
|
end
|
|
428
428
|
|
|
429
429
|
def create_clear_all_breakpoints_action
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
action = Qt::Action.new(tr("Clear All Breakpoints"), self)
|
|
431
|
+
action.statusTip = tr("Clear all existing breakpoints")
|
|
432
|
+
action.connect(SIGNAL('triggered()')) do
|
|
433
433
|
clear_breakpoints
|
|
434
434
|
emit breakpoints_cleared
|
|
435
435
|
end
|
|
436
|
-
|
|
436
|
+
action
|
|
437
437
|
end
|
|
438
438
|
|
|
439
439
|
# Get the top and bottom coordinates of the block in viewport coordinates
|
|
@@ -76,16 +76,16 @@ module Cosmos
|
|
|
76
76
|
return result
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def save_file_dialog(directory = Cosmos::USERPATH, message = "Save File")
|
|
80
|
-
_get_main_thread_gui {|window| Qt::FileDialog.getSaveFileName(window, message, directory) }
|
|
79
|
+
def save_file_dialog(directory = Cosmos::USERPATH, message = "Save File", filter = "All Files (*.*)")
|
|
80
|
+
_get_main_thread_gui {|window| Qt::FileDialog.getSaveFileName(window, message, directory, filter) }
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
def open_file_dialog(directory = Cosmos::USERPATH, message = "Open File")
|
|
84
|
-
_get_main_thread_gui {|window| Qt::FileDialog.getOpenFileName(window, message, directory) }
|
|
83
|
+
def open_file_dialog(directory = Cosmos::USERPATH, message = "Open File", filter = "All Files (*.*)")
|
|
84
|
+
_get_main_thread_gui {|window| Qt::FileDialog.getOpenFileName(window, message, directory, filter) }
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
def open_files_dialog(directory = Cosmos::USERPATH, message = "Open File(s)")
|
|
88
|
-
_get_main_thread_gui {|window| Qt::FileDialog.getOpenFileNames(window, message, directory) }
|
|
87
|
+
def open_files_dialog(directory = Cosmos::USERPATH, message = "Open File(s)", filter = "All Files (*.*)")
|
|
88
|
+
_get_main_thread_gui {|window| Qt::FileDialog.getOpenFileNames(window, message, directory, filter) }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def open_directory_dialog(directory = Cosmos::USERPATH, message = "Open Directory")
|
|
@@ -134,7 +134,7 @@ module Cosmos
|
|
|
134
134
|
result = nil
|
|
135
135
|
_get_main_thread_gui do |window|
|
|
136
136
|
msg = Qt::MessageBox.new(window)
|
|
137
|
-
msg.setIcon(Qt::MessageBox::
|
|
137
|
+
msg.setIcon(Qt::MessageBox::Question)
|
|
138
138
|
msg.setText(message)
|
|
139
139
|
msg.setWindowTitle(title)
|
|
140
140
|
msg.setStandardButtons(Qt::MessageBox::Yes | Qt::MessageBox::No)
|
|
@@ -221,7 +221,7 @@ module Cosmos
|
|
|
221
221
|
|
|
222
222
|
def prompt_vertical_message_box(string, buttons)
|
|
223
223
|
loop do
|
|
224
|
-
result = buttons[0]
|
|
224
|
+
result = buttons[0].clone
|
|
225
225
|
Qt.execute_in_main_thread(true, 0.05) do
|
|
226
226
|
dialog = _build_dialog(string)
|
|
227
227
|
|
|
@@ -258,7 +258,7 @@ module Cosmos
|
|
|
258
258
|
|
|
259
259
|
def prompt_combo_box(string, options)
|
|
260
260
|
loop do
|
|
261
|
-
result = options[0]
|
|
261
|
+
result = options[0].clone
|
|
262
262
|
Qt.execute_in_main_thread(true, 0.05) do
|
|
263
263
|
dialog = _build_dialog(string)
|
|
264
264
|
# Check if the last parameter is false which means they don't want
|
|
@@ -15,25 +15,20 @@ require 'cosmos'
|
|
|
15
15
|
require 'cosmos/gui/qt'
|
|
16
16
|
|
|
17
17
|
module Cosmos
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# This class implements the RealtimeButtonBar
|
|
22
|
-
#
|
|
18
|
+
# Create a horizontal or vertical widget which contains buttons to control
|
|
19
|
+
# scripting. The Step button is hidden by default, and Start, Pause, and
|
|
20
|
+
# Stop are visible.
|
|
23
21
|
class RealtimeButtonBar < Qt::Widget
|
|
24
|
-
|
|
25
|
-
# Accessors for callbacks
|
|
22
|
+
attr_accessor :step_callback
|
|
26
23
|
attr_accessor :start_callback
|
|
27
24
|
attr_accessor :pause_callback
|
|
28
25
|
attr_accessor :stop_callback
|
|
29
|
-
|
|
30
|
-
# Readers for buttons
|
|
26
|
+
attr_reader :step_button
|
|
31
27
|
attr_reader :start_button
|
|
32
28
|
attr_reader :pause_button
|
|
33
29
|
attr_reader :stop_button
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
def initialize (parent, orientation = Qt::Horizontal)
|
|
31
|
+
def initialize(parent, orientation = Qt::Horizontal)
|
|
37
32
|
super(parent)
|
|
38
33
|
if orientation == Qt::Horizontal
|
|
39
34
|
# Horizontal Frame for overall widget
|
|
@@ -56,15 +51,21 @@ module Cosmos
|
|
|
56
51
|
@stop_button = Qt::PushButton.new('Stop')
|
|
57
52
|
@pause_button = Qt::PushButton.new('Pause')
|
|
58
53
|
@start_button = Qt::PushButton.new('Start')
|
|
54
|
+
@step_button = Qt::PushButton.new('Step')
|
|
55
|
+
@step_button.setHidden(true)
|
|
56
|
+
@overall_frame.addWidget(@step_button)
|
|
59
57
|
@overall_frame.addWidget(@start_button)
|
|
60
58
|
@overall_frame.addWidget(@pause_button)
|
|
61
59
|
@overall_frame.addWidget(@stop_button)
|
|
62
60
|
else
|
|
63
61
|
# Buttons
|
|
64
62
|
@button_frame = Qt::HBoxLayout.new
|
|
63
|
+
@step_button = Qt::PushButton.new('Step')
|
|
64
|
+
@step_button.setHidden(true)
|
|
65
65
|
@start_button = Qt::PushButton.new('Start')
|
|
66
66
|
@pause_button = Qt::PushButton.new('Pause')
|
|
67
67
|
@stop_button = Qt::PushButton.new('Stop')
|
|
68
|
+
@button_frame.addWidget(@step_button)
|
|
68
69
|
@button_frame.addWidget(@start_button)
|
|
69
70
|
@button_frame.addWidget(@pause_button)
|
|
70
71
|
@button_frame.addWidget(@stop_button)
|
|
@@ -75,9 +76,11 @@ module Cosmos
|
|
|
75
76
|
|
|
76
77
|
# Connect handlers
|
|
77
78
|
@stop_button.connect(SIGNAL('clicked()')) { @stop_callback.call if @stop_callback }
|
|
78
|
-
@pause_button.connect(SIGNAL('clicked()'))
|
|
79
|
-
@start_button.connect(SIGNAL('clicked()'))
|
|
79
|
+
@pause_button.connect(SIGNAL('clicked()')) { @pause_callback.call if @pause_callback }
|
|
80
|
+
@start_button.connect(SIGNAL('clicked()')) { @start_callback.call if @start_callback }
|
|
81
|
+
@step_button.connect(SIGNAL('clicked()')) { @step_callback.call if @step_callback }
|
|
80
82
|
|
|
83
|
+
@step_callback = nil
|
|
81
84
|
@start_callback = nil
|
|
82
85
|
@pause_callback = nil
|
|
83
86
|
@stop_callback = nil
|
|
@@ -92,7 +95,5 @@ module Cosmos
|
|
|
92
95
|
def state= (new_state)
|
|
93
96
|
@state.setText(new_state.to_s)
|
|
94
97
|
end
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
end # module Cosmos
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -53,7 +53,7 @@ module Cosmos
|
|
|
53
53
|
|
|
54
54
|
# Identifies an unknown buffer of data as a Packet. The raw data is
|
|
55
55
|
# returned but the packet that matched is recorded so it can be set in the
|
|
56
|
-
#
|
|
56
|
+
# read_packet callback.
|
|
57
57
|
#
|
|
58
58
|
# @return [String|Symbol] The identified packet data or :STOP if more data
|
|
59
59
|
# is required to build a packet
|
|
@@ -82,7 +82,7 @@ module Cosmos
|
|
|
82
82
|
return :STOP if @data.length < identified_packet.defined_length
|
|
83
83
|
end
|
|
84
84
|
# Set some variables so we can update the packet in
|
|
85
|
-
#
|
|
85
|
+
# read_packet
|
|
86
86
|
@received_time = Time.now.sys
|
|
87
87
|
@target_name = identified_packet.target_name
|
|
88
88
|
@packet_name = identified_packet.packet_name
|
|
@@ -121,6 +121,9 @@ module Cosmos
|
|
|
121
121
|
# Grab the response packet specified in the command
|
|
122
122
|
result_packet = System.telemetry.packet(@interface.target_names[0], @response_packet).clone
|
|
123
123
|
result_packet.received_time = nil
|
|
124
|
+
result_packet.id_items.each do |item|
|
|
125
|
+
result_packet.write_item(item, item.id_value, :RAW)
|
|
126
|
+
end
|
|
124
127
|
|
|
125
128
|
# Convert the response template into a Regexp
|
|
126
129
|
response_item_names = []
|
|
@@ -75,18 +75,29 @@ module Cosmos
|
|
|
75
75
|
# the constructor and a new {UdpReadSocket} if the read_port was given in
|
|
76
76
|
# the constructor.
|
|
77
77
|
def connect
|
|
78
|
-
@
|
|
79
|
-
@
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@
|
|
87
|
-
|
|
88
|
-
@
|
|
89
|
-
|
|
78
|
+
if @read_port and @write_dest_port and @write_src_port and (@read_port == @write_src_port)
|
|
79
|
+
@read_socket = UdpReadWriteSocket.new(
|
|
80
|
+
@read_port,
|
|
81
|
+
@bind_address,
|
|
82
|
+
@write_dest_port,
|
|
83
|
+
@hostname,
|
|
84
|
+
@interface_address,
|
|
85
|
+
@ttl)
|
|
86
|
+
@write_socket = @read_socket
|
|
87
|
+
else
|
|
88
|
+
@read_socket = UdpReadSocket.new(
|
|
89
|
+
@read_port,
|
|
90
|
+
@hostname,
|
|
91
|
+
@interface_address,
|
|
92
|
+
@bind_address) if @read_port
|
|
93
|
+
@write_socket = UdpWriteSocket.new(
|
|
94
|
+
@hostname,
|
|
95
|
+
@write_dest_port,
|
|
96
|
+
@write_src_port,
|
|
97
|
+
@interface_address,
|
|
98
|
+
@ttl,
|
|
99
|
+
@bind_address) if @write_dest_port
|
|
100
|
+
end
|
|
90
101
|
@thread_sleeper = nil
|
|
91
102
|
end
|
|
92
103
|
|
|
@@ -105,9 +116,11 @@ module Cosmos
|
|
|
105
116
|
|
|
106
117
|
# Close the active ports (read and/or write) and set the sockets to nil.
|
|
107
118
|
def disconnect
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
if @write_socket != @read_socket
|
|
120
|
+
Cosmos.close_socket(@write_socket)
|
|
121
|
+
end
|
|
110
122
|
Cosmos.close_socket(@read_socket)
|
|
123
|
+
@write_socket = nil
|
|
111
124
|
@read_socket = nil
|
|
112
125
|
@thread_sleeper.cancel if @thread_sleeper
|
|
113
126
|
@thread_sleeper = nil
|
data/lib/cosmos/io/json_drb.rb
CHANGED
|
@@ -15,6 +15,28 @@ require 'drb/acl'
|
|
|
15
15
|
require 'drb/drb'
|
|
16
16
|
require 'set'
|
|
17
17
|
require 'cosmos/io/json_rpc'
|
|
18
|
+
require 'cosmos/io/json_drb_rack'
|
|
19
|
+
require 'rack/handler/puma'
|
|
20
|
+
if RUBY_ENGINE == 'ruby' and %w(2.2.7 2.2.8 2.3.4 2.4.1).include? RUBY_VERSION
|
|
21
|
+
require 'stopgap_13632'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Add methods to the Puma::Launcher and Puma::Single class so we can tell
|
|
25
|
+
# if the server has been started.
|
|
26
|
+
module Puma
|
|
27
|
+
class Launcher
|
|
28
|
+
def running
|
|
29
|
+
@runner and @runner.running
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
class Runner
|
|
33
|
+
end
|
|
34
|
+
class Single < Runner
|
|
35
|
+
def running
|
|
36
|
+
@server and @server.running
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
18
40
|
|
|
19
41
|
module Cosmos
|
|
20
42
|
|
|
@@ -25,7 +47,6 @@ module Cosmos
|
|
|
25
47
|
# methods.
|
|
26
48
|
class JsonDRb
|
|
27
49
|
MINIMUM_REQUEST_TIME = 0.0001
|
|
28
|
-
FAST_READ = (RUBY_VERSION > "2.1")
|
|
29
50
|
|
|
30
51
|
@@debug = false
|
|
31
52
|
|
|
@@ -37,7 +58,6 @@ module Cosmos
|
|
|
37
58
|
attr_accessor :acl
|
|
38
59
|
|
|
39
60
|
def initialize
|
|
40
|
-
@listen_socket = nil
|
|
41
61
|
@thread = nil
|
|
42
62
|
@acl = nil
|
|
43
63
|
@object = nil
|
|
@@ -46,52 +66,46 @@ module Cosmos
|
|
|
46
66
|
@request_times = []
|
|
47
67
|
@request_times_index = 0
|
|
48
68
|
@request_mutex = Mutex.new
|
|
49
|
-
@
|
|
50
|
-
@
|
|
51
|
-
@client_pipe_writers = []
|
|
52
|
-
@client_mutex = Mutex.new
|
|
53
|
-
@thread_reader, @thread_writer = IO.pipe
|
|
69
|
+
@server = nil
|
|
70
|
+
@server_mutex = Mutex.new
|
|
54
71
|
end
|
|
55
72
|
|
|
56
73
|
# Returns the number of connected clients
|
|
57
74
|
# @return [Integer] The number of connected clients
|
|
58
75
|
def num_clients
|
|
59
|
-
|
|
76
|
+
clients = 0
|
|
77
|
+
@server_mutex.synchronize do
|
|
78
|
+
if @server
|
|
79
|
+
# @server.stats() returns a string like: { "backlog": 0, "running": 0 }
|
|
80
|
+
# "running" indicates the number of server threads running, and
|
|
81
|
+
# therefore the number of clients connected.
|
|
82
|
+
stats = @server.stats()
|
|
83
|
+
stats =~ /"running": \d*/
|
|
84
|
+
clients = $&.split(":")[1].to_i
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
return clients
|
|
60
88
|
end
|
|
61
89
|
|
|
62
90
|
# Stops the DRb service by closing the socket and the processing thread
|
|
63
91
|
def stop_service
|
|
64
|
-
|
|
92
|
+
# Kill the server thread; it can take a while, so use
|
|
93
|
+
# graceful_timeout = 5, timeout_interval = 0.1, hard_timeout = 5
|
|
94
|
+
Cosmos.kill_thread(self, @thread, 5, 0.1, 5)
|
|
65
95
|
@thread = nil
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
client_threads = nil
|
|
69
|
-
@client_mutex.synchronize do
|
|
70
|
-
@client_sockets.each do |client_socket|
|
|
71
|
-
Cosmos.close_socket(client_socket)
|
|
72
|
-
end
|
|
73
|
-
@client_pipe_writers.each do |client_pipe_writer|
|
|
74
|
-
client_pipe_writer.write('.')
|
|
75
|
-
end
|
|
76
|
-
client_threads = @client_threads.clone
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# This cannot be inside of the client_mutex or the threads will not
|
|
80
|
-
# be able to shutdown because they will stick on the client_mutex
|
|
81
|
-
client_threads.each do |client_thread|
|
|
82
|
-
Cosmos.kill_thread(self, client_thread)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
@client_mutex.synchronize do
|
|
86
|
-
@client_threads.clear
|
|
87
|
-
@client_sockets.clear
|
|
88
|
-
@client_pipe_writers.clear
|
|
96
|
+
@server_mutex.synchronize do
|
|
97
|
+
@server = nil
|
|
89
98
|
end
|
|
90
99
|
end
|
|
91
100
|
|
|
92
101
|
# Gracefully kill the thread
|
|
93
102
|
def graceful_kill
|
|
94
|
-
@
|
|
103
|
+
@server_mutex.synchronize do
|
|
104
|
+
begin
|
|
105
|
+
@server.stop if @server and @server.running
|
|
106
|
+
rescue
|
|
107
|
+
end
|
|
108
|
+
end
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
# @param hostname [String] The host to start the service on
|
|
@@ -99,58 +113,80 @@ module Cosmos
|
|
|
99
113
|
# @param object [Object] The object to send the DRb requests to. This
|
|
100
114
|
# object must either include the Cosmos::Script module or be the
|
|
101
115
|
# CmdTlmServer.
|
|
102
|
-
def start_service(hostname = nil, port = nil, object = nil)
|
|
116
|
+
def start_service(hostname = nil, port = nil, object = nil, max_threads = 1000)
|
|
117
|
+
server_started = false
|
|
118
|
+
@server_mutex.synchronize do
|
|
119
|
+
server_started = true if @server
|
|
120
|
+
end
|
|
121
|
+
return if server_started
|
|
122
|
+
|
|
103
123
|
if hostname and port and object
|
|
104
|
-
@thread_reader, @thread_writer = IO.pipe
|
|
105
124
|
@object = object
|
|
106
125
|
hostname = '127.0.0.1'.freeze if (hostname.to_s.upcase == 'LOCALHOST'.freeze)
|
|
107
126
|
|
|
108
|
-
# Create a socket to accept connections from clients
|
|
109
|
-
begin
|
|
110
|
-
@listen_socket = TCPServer.new(hostname, port)
|
|
111
|
-
@listen_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) unless Kernel.is_windows?
|
|
112
|
-
# The address is use error is pretty typical if an existing
|
|
113
|
-
# CmdTlmServer is running so explicitly rescue this
|
|
114
|
-
rescue Errno::EADDRINUSE
|
|
115
|
-
raise "Error binding to port #{port}.\n" +
|
|
116
|
-
"Either another application is using this port\n" +
|
|
117
|
-
"or the operating system is being slow cleaning up.\n" +
|
|
118
|
-
"Make sure all sockets/streams are closed in all applications,\n" +
|
|
119
|
-
"wait 1 minute and try again."
|
|
120
|
-
# Something else went wrong which is fatal
|
|
121
|
-
rescue => error
|
|
122
|
-
Logger.error "JsonDRb listen thread unable to be created.\n#{error.formatted}"
|
|
123
|
-
Cosmos.handle_fatal_exception(error)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Start the listen thread which accepts connections
|
|
127
127
|
@thread = Thread.new do
|
|
128
|
+
|
|
129
|
+
# Create an http server to accept requests from clients
|
|
128
130
|
begin
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
server_config = {
|
|
132
|
+
:Host => hostname,
|
|
133
|
+
:Port => port,
|
|
134
|
+
:Silent => true,
|
|
135
|
+
:Verbose => false,
|
|
136
|
+
:Threads => "0:#{max_threads}",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# The run call will block until the server is stopped.
|
|
140
|
+
Rack::Handler::Puma.run(JsonDrbRack.new(self), server_config) do |server|
|
|
141
|
+
@server_mutex.synchronize do
|
|
142
|
+
@server = server
|
|
140
143
|
end
|
|
144
|
+
end
|
|
141
145
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
# Wait for all puma threads to stop before trying to close
|
|
147
|
+
# the sockets
|
|
148
|
+
start_time = Time.now
|
|
149
|
+
while true
|
|
150
|
+
puma_threads = false
|
|
151
|
+
Thread.list.each {|thread| puma_threads = true if thread.inspect.match(/puma/)}
|
|
152
|
+
break if !puma_threads
|
|
153
|
+
break if (Time.now - start_time) > 10.0
|
|
154
|
+
sleep 0.25
|
|
148
155
|
end
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
|
|
157
|
+
# Puma doesn't clean up it's own sockets after shutting down,
|
|
158
|
+
# so we'll do that here.
|
|
159
|
+
@server_mutex.synchronize do
|
|
160
|
+
@server.binder.close() if @server
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The address in use error is pretty typical if an existing
|
|
164
|
+
# CmdTlmServer is running so explicitly rescue this
|
|
165
|
+
rescue Errno::EADDRINUSE
|
|
166
|
+
@server = nil
|
|
167
|
+
raise "Error binding to port #{port}.\n" +
|
|
168
|
+
"Either another application is using this port\n" +
|
|
169
|
+
"or the operating system is being slow cleaning up.\n" +
|
|
170
|
+
"Make sure all sockets/streams are closed in all applications,\n" +
|
|
171
|
+
"wait 1 minute and try again."
|
|
172
|
+
# Something else went wrong which is fatal
|
|
173
|
+
rescue => error
|
|
174
|
+
@server = nil
|
|
175
|
+
Logger.error "JsonDRb http server could not be started or unexpectedly died.\n#{error.formatted}"
|
|
151
176
|
Cosmos.handle_fatal_exception(error)
|
|
152
177
|
end
|
|
153
178
|
end
|
|
179
|
+
|
|
180
|
+
# Wait for the server to be started in the thread before returning.
|
|
181
|
+
start_time = Time.now
|
|
182
|
+
while ((Time.now - start_time) < 5.0) and !server_started
|
|
183
|
+
sleep(0.1)
|
|
184
|
+
@server_mutex.synchronize do
|
|
185
|
+
server_started = true if @server and @server.running
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
raise "JsonDRb http server could not be started." unless server_started
|
|
189
|
+
|
|
154
190
|
elsif hostname or port or object
|
|
155
191
|
raise "0 or 3 parameters must be given"
|
|
156
192
|
else
|
|
@@ -188,81 +224,6 @@ module Cosmos
|
|
|
188
224
|
avg
|
|
189
225
|
end
|
|
190
226
|
|
|
191
|
-
# @param socket [Socket] The socket to the client
|
|
192
|
-
# @param data [String] Binary data which has already been read from the
|
|
193
|
-
# socket.
|
|
194
|
-
# @param pipe_reader [IO.pipe] Used to break out of select
|
|
195
|
-
# @return [String] The request message
|
|
196
|
-
def self.receive_message(socket, data, pipe_reader)
|
|
197
|
-
self.get_at_least_x_bytes_of_data(socket, data, 4, pipe_reader)
|
|
198
|
-
if data.length >= 4
|
|
199
|
-
length = data[0..3].unpack('N'.freeze)[0]
|
|
200
|
-
data.replace(data[4..-1])
|
|
201
|
-
else
|
|
202
|
-
return nil
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
self.get_at_least_x_bytes_of_data(socket, data, length, pipe_reader)
|
|
206
|
-
if data.length >= length
|
|
207
|
-
message = data[0..(length - 1)]
|
|
208
|
-
data.replace(data[length..-1])
|
|
209
|
-
return message
|
|
210
|
-
else
|
|
211
|
-
return nil
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# @param socket [Socket] The socket to the client
|
|
216
|
-
# @param current_data [String] Binary data read from the socket
|
|
217
|
-
# @param required_num_bytes [Integer] The minimum number of bytes to read
|
|
218
|
-
# @param pipe_reader [IO.pipe] Used to break out of select
|
|
219
|
-
# before returning
|
|
220
|
-
def self.get_at_least_x_bytes_of_data(socket, current_data, required_num_bytes, pipe_reader)
|
|
221
|
-
while (current_data.length < required_num_bytes)
|
|
222
|
-
if FAST_READ
|
|
223
|
-
data = socket.read_nonblock(65535, exception: false)
|
|
224
|
-
raise EOFError, 'end of file reached' unless data
|
|
225
|
-
if data == :wait_readable
|
|
226
|
-
IO.fast_select([socket, pipe_reader], nil, nil, nil)
|
|
227
|
-
else
|
|
228
|
-
current_data << data
|
|
229
|
-
end
|
|
230
|
-
else
|
|
231
|
-
begin
|
|
232
|
-
current_data << socket.read_nonblock(65535)
|
|
233
|
-
rescue IO::WaitReadable
|
|
234
|
-
IO.fast_select([socket, pipe_reader], nil, nil, nil)
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# @param socket [Socket] The socket to the client
|
|
241
|
-
# @param data [String] Binary data to send to the socket
|
|
242
|
-
# @param send_timeout [Float] The number of seconds to wait for the send to
|
|
243
|
-
# complete
|
|
244
|
-
def self.send_data(socket, data, send_timeout = 10.0)
|
|
245
|
-
num_bytes_to_send = data.length + 4
|
|
246
|
-
total_bytes_sent = 0
|
|
247
|
-
bytes_sent = 0
|
|
248
|
-
data_to_send = [data.length].pack('N'.freeze) << data.clone
|
|
249
|
-
|
|
250
|
-
loop do
|
|
251
|
-
begin
|
|
252
|
-
bytes_sent = socket.write_nonblock(data_to_send[total_bytes_sent..-1])
|
|
253
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
254
|
-
result = IO.fast_select(nil, [socket], nil, send_timeout)
|
|
255
|
-
if result
|
|
256
|
-
retry
|
|
257
|
-
else
|
|
258
|
-
raise Timeout::Error, "Send Timeout"
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
total_bytes_sent += bytes_sent
|
|
262
|
-
break if total_bytes_sent >= num_bytes_to_send
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
227
|
# @return [Boolean] Whether debug messages are enabled
|
|
267
228
|
def self.debug?
|
|
268
229
|
@@debug
|
|
@@ -273,66 +234,20 @@ module Cosmos
|
|
|
273
234
|
@@debug = value
|
|
274
235
|
end
|
|
275
236
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
# Creates a new Thread to service the JSON DRb requests from the client.
|
|
279
|
-
#
|
|
280
|
-
# @param socket [Socket] The socket which the server accepted from the
|
|
281
|
-
# client.
|
|
282
|
-
def create_client_thread(socket)
|
|
283
|
-
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
284
|
-
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
|
|
285
|
-
|
|
286
|
-
Thread.new(socket) do |my_socket|
|
|
287
|
-
pipe_reader, pipe_writer = IO.pipe
|
|
288
|
-
@client_mutex.synchronize do
|
|
289
|
-
@client_sockets << my_socket
|
|
290
|
-
@client_threads << Thread.current
|
|
291
|
-
@client_pipe_writers << pipe_writer
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
data = ''
|
|
295
|
-
|
|
296
|
-
begin
|
|
297
|
-
while true
|
|
298
|
-
begin
|
|
299
|
-
request_data = JsonDRb.receive_message(my_socket, data, pipe_reader)
|
|
300
|
-
start_time = Time.now.sys
|
|
301
|
-
@request_count += 1
|
|
302
|
-
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ENOTSOCK, IOError
|
|
303
|
-
# Socket was closed
|
|
304
|
-
break
|
|
305
|
-
end
|
|
306
|
-
if request_data
|
|
307
|
-
break unless process_request(request_data, my_socket, start_time)
|
|
308
|
-
else
|
|
309
|
-
# Socket was closed by client
|
|
310
|
-
break
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
rescue Exception => error
|
|
314
|
-
Logger.error "JsonDrb client thread unexpectedly died.\n#{error.formatted}"
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
@client_mutex.synchronize do
|
|
318
|
-
Cosmos.close_socket(my_socket)
|
|
319
|
-
@client_sockets.delete(my_socket)
|
|
320
|
-
@client_threads.delete(Thread.current)
|
|
321
|
-
@client_pipe_writers.delete(pipe_writer)
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Process the JSON request data, execute the method, and send the response.
|
|
237
|
+
# Process the JSON request data, execute the method, and create a response.
|
|
327
238
|
#
|
|
328
239
|
# @param request_data [String] The JSON encoded request
|
|
329
|
-
# @param my_socket [Socket] The socket to send the response out on
|
|
330
240
|
# @param start_time [Time] The time when the initial request was received
|
|
331
|
-
|
|
241
|
+
# @return response_data, error_code [String, Integer/nil] The JSON encoded
|
|
242
|
+
# response and error code
|
|
243
|
+
def process_request(request_data, start_time)
|
|
244
|
+
@request_count += 1
|
|
332
245
|
STDOUT.puts request_data if JsonDRb.debug?
|
|
333
246
|
begin
|
|
334
247
|
request = JsonRpcRequest.from_json(request_data)
|
|
335
248
|
response = nil
|
|
249
|
+
error_code = nil
|
|
250
|
+
response_data = nil
|
|
336
251
|
|
|
337
252
|
if (@method_whitelist and @method_whitelist.include?(request.method)) or
|
|
338
253
|
(!@method_whitelist and !JsonRpcRequest::DANGEROUS_METHODS.include?(request.method))
|
|
@@ -344,41 +259,46 @@ module Cosmos
|
|
|
344
259
|
rescue Exception => error
|
|
345
260
|
if request.id
|
|
346
261
|
if NoMethodError === error
|
|
262
|
+
error_code = JsonRpcError::ErrorCode::METHOD_NOT_FOUND
|
|
347
263
|
response = JsonRpcErrorResponse.new(
|
|
348
|
-
JsonRpcError.new(
|
|
264
|
+
JsonRpcError.new(error_code, "Method not found", error), request.id)
|
|
349
265
|
elsif ArgumentError === error
|
|
266
|
+
error_code = JsonRpcError::ErrorCode::INVALID_PARAMS
|
|
350
267
|
response = JsonRpcErrorResponse.new(
|
|
351
|
-
JsonRpcError.new(
|
|
268
|
+
JsonRpcError.new(error_code, "Invalid params", error), request.id)
|
|
352
269
|
else
|
|
270
|
+
error_code = JsonRpcError::ErrorCode::OTHER_ERROR
|
|
353
271
|
response = JsonRpcErrorResponse.new(
|
|
354
|
-
JsonRpcError.new(
|
|
272
|
+
JsonRpcError.new(error_code, error.message, error), request.id)
|
|
355
273
|
end
|
|
356
274
|
end
|
|
357
275
|
end
|
|
358
276
|
else
|
|
359
277
|
if request.id
|
|
278
|
+
error_code = JsonRpcError::ErrorCode::OTHER_ERROR
|
|
360
279
|
response = JsonRpcErrorResponse.new(
|
|
361
|
-
JsonRpcError.new(
|
|
280
|
+
JsonRpcError.new(error_code, "Cannot call unauthorized methods"), request.id)
|
|
362
281
|
end
|
|
363
282
|
end
|
|
364
|
-
process_response(response,
|
|
283
|
+
response_data = process_response(response, start_time) if response
|
|
284
|
+
return response_data, error_code
|
|
365
285
|
rescue => error
|
|
366
|
-
|
|
367
|
-
|
|
286
|
+
error_code = JsonRpcError::ErrorCode::INVALID_REQUEST
|
|
287
|
+
response = JsonRpcErrorResponse.new(JsonRpcError.new(error_code, "Invalid Request", error), nil)
|
|
288
|
+
response_data = process_response(response, start_time)
|
|
289
|
+
return response_data, error_code
|
|
368
290
|
end
|
|
369
|
-
true
|
|
370
291
|
end
|
|
371
292
|
|
|
372
|
-
|
|
293
|
+
protected
|
|
294
|
+
|
|
295
|
+
def process_response(response, start_time)
|
|
373
296
|
response_data = response.to_json(:allow_nan => true)
|
|
374
297
|
STDOUT.puts response_data if JsonDRb.debug?
|
|
375
|
-
JsonDRb.send_data(socket, response_data)
|
|
376
298
|
end_time = Time.now.sys
|
|
377
299
|
request_time = end_time - start_time
|
|
378
300
|
add_request_time(request_time)
|
|
379
|
-
|
|
380
|
-
# Socket was closed?
|
|
381
|
-
return false
|
|
301
|
+
return response_data
|
|
382
302
|
end
|
|
383
303
|
|
|
384
304
|
end
|