Skip to content

Commit e8c5657

Browse files
authored
[ycabled] fix exception-handling logic for ycabled (sonic-net#306)
1 parent 905874d commit e8c5657

File tree

3 files changed

+182
-69
lines changed

3 files changed

+182
-69
lines changed

sonic-ycabled/tests/test_ycable.py

+61-23
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import sys
1010
import time
11+
import traceback
1112

1213
if sys.version_info >= (3, 3):
1314
from unittest.mock import MagicMock, patch
@@ -40,20 +41,20 @@ def test_ycable_info_helper_class_run(self, mocked_sleep):
4041
with patch('ycable.ycable.platform_sfputil') as patched_util:
4142
patched_util.logical.return_value = ['Ethernet0', 'Ethernet4']
4243
patched_util.get_asic_id_for_logical_port.return_value = 0
43-
Y_cable_state_task = YcableStateUpdateTask()
44-
Y_cable_state_task.task_process = MagicMock()
45-
Y_cable_state_task.task_stopping_event = MagicMock()
44+
y_cable_presence = [True]
4645
stopping_event = MagicMock()
4746
sfp_error_event = MagicMock()
48-
y_cable_presence = [True]
49-
Y_cable_state_task.task_run(sfp_error_event, y_cable_presence)
50-
Y_cable_state_task.task_stop()
51-
Y_cable_task = YcableInfoUpdateTask()
47+
Y_cable_state_task = YcableStateUpdateTask(sfp_error_event, y_cable_presence)
48+
Y_cable_state_task.task_process = MagicMock()
49+
Y_cable_state_task.task_stopping_event = MagicMock()
50+
Y_cable_state_task.start()
51+
Y_cable_state_task.join()
52+
Y_cable_task = YcableInfoUpdateTask(y_cable_presence)
5253
Y_cable_task.task_thread = MagicMock()
5354
Y_cable_task.task_stopping_event = MagicMock()
5455
Y_cable_task.task_stopping_event.is_set = MagicMock()
55-
Y_cable_task.task_run(y_cable_presence)
56-
Y_cable_task.task_stop()
56+
Y_cable_task.start()
57+
Y_cable_task.join()
5758
Y_cable_state_task.task_stopping_event.return_value.is_set.return_value = True
5859
#Y_cable_state_task.task_worker(stopping_event, sfp_error_event, y_cable_presence)
5960
# For now just check if exception is thrown for UT purposes
@@ -67,19 +68,20 @@ def test_ycable_info_helper_class_run(self, mocked_sleep):
6768
@patch("swsscommon.swsscommon.Select.select", MagicMock())
6869
def test_ycable_helper_class_run_loop(self):
6970
Y_cable_task = YCableTableUpdateTask()
71+
Y_cable_cli_task = YCableCliUpdateTask()
7072
Y_cable_task.task_stopping_event = MagicMock()
73+
Y_cable_cli_task.task_stopping_event = MagicMock()
7174
Y_cable_task.task_thread = MagicMock()
7275
Y_cable_task.task_thread.start = MagicMock()
7376
Y_cable_task.task_thread.join = MagicMock()
74-
Y_cable_task.task_cli_thead = MagicMock()
75-
Y_cable_task.task_cli_thead.start = MagicMock()
76-
Y_cable_task.task_cli_thead.join = MagicMock()
7777
#Y_cable_task.task_stopping_event.return_value.is_set.return_value = False
7878
swsscommon.SubscriberStateTable.return_value.pop.return_value = (True, True, {"read_side": "2"})
7979
Y_cable_task.task_worker()
80-
Y_cable_task.task_cli_worker()
81-
Y_cable_task.task_run()
82-
Y_cable_task.task_stop()
80+
Y_cable_task.start()
81+
Y_cable_task.join()
82+
Y_cable_cli_task.task_cli_worker()
83+
Y_cable_cli_task.start()
84+
Y_cable_cli_task.join()
8385

8486
@patch("swsscommon.swsscommon.Select", MagicMock())
8587
@patch("swsscommon.swsscommon.Select.addSelectable", MagicMock())
@@ -89,14 +91,10 @@ def test_ycable_helper_class_run(self):
8991
Y_cable_task.task_thread = MagicMock()
9092
Y_cable_task.task_thread.start = MagicMock()
9193
Y_cable_task.task_thread.join = MagicMock()
92-
Y_cable_task.task_cli_thead = MagicMock()
93-
Y_cable_task.task_cli_thead.start = MagicMock()
94-
Y_cable_task.task_cli_thead.join = MagicMock()
9594
Y_cable_task.task_stopping_event.return_value.is_set.return_value = True
9695
Y_cable_task.task_worker()
97-
Y_cable_task.task_cli_worker()
98-
Y_cable_task.task_run()
99-
Y_cable_task.task_stop()
96+
Y_cable_task.start()
97+
Y_cable_task.join()
10098

10199
def test_detect_port_in_error_status(self):
102100

@@ -291,9 +289,7 @@ def test_DaemonYcable_init_deinit(self):
291289
@patch('ycable.ycable.platform_sfputil', MagicMock())
292290
@patch('ycable.ycable.DaemonYcable.load_platform_util', MagicMock())
293291
@patch('ycable.ycable.YcableInfoUpdateTask', MagicMock())
294-
@patch('ycable.ycable.YcableInfoUpdateTask.task_run', MagicMock())
295292
@patch('ycable.ycable.YcableStateUpdateTask', MagicMock())
296-
@patch('ycable.ycable.YcableStateUpdateTask.task_run', MagicMock())
297293
@patch('ycable.ycable_utilities.y_cable_helper.init_ports_status_for_y_cable', MagicMock())
298294
def test_DaemonYcable_init_deinit_full(self):
299295
ycable = DaemonYcable(SYSLOG_IDENTIFIER)
@@ -327,3 +323,45 @@ def wait_until(total_wait_time, interval, call_back, *args, **kwargs):
327323
time.sleep(interval)
328324
wait_time += interval
329325
return False
326+
327+
328+
class TestYcableScriptException(object):
329+
330+
@patch("swsscommon.swsscommon.Select", MagicMock(side_effect=NotImplementedError))
331+
@patch("swsscommon.swsscommon.Select.addSelectable", MagicMock(side_effect=NotImplementedError))
332+
@patch("swsscommon.swsscommon.Select.select", MagicMock(side_effect=NotImplementedError))
333+
def test_ycable_helper_class_run_loop_with_exception(self):
334+
335+
336+
337+
Y_cable_cli_task = YCableCliUpdateTask()
338+
expected_exception_start = None
339+
expected_exception_join = None
340+
trace = None
341+
try:
342+
Y_cable_cli_task.start()
343+
Y_cable_cli_task.task_cli_worker()
344+
except Exception as e1:
345+
expected_exception_start = e1
346+
trace = traceback.format_exc()
347+
348+
349+
try:
350+
Y_cable_cli_task.join()
351+
except Exception as e2:
352+
expected_exception_join = e2
353+
354+
"""
355+
#Handy debug Helpers or else use import logging
356+
#f = open("newfile", "w")
357+
#f.write(format(e2))
358+
#f.write(format(m1))
359+
#f.write(trace)
360+
"""
361+
362+
assert(type(expected_exception_start) == type(expected_exception_join))
363+
assert(expected_exception_start.args == expected_exception_join.args)
364+
assert("NotImplementedError" in str(trace) and "effect" in str(trace))
365+
assert("sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py" in str(trace))
366+
assert("swsscommon.Select" in str(trace))
367+

sonic-ycabled/ycable/ycable.py

+72-29
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
"""
77

88
try:
9+
import os
910
import signal
1011
import sys
1112
import time
1213
import threading
14+
import traceback
1315

1416
from enum import Enum
1517
from sonic_py_common import daemon_base, device_info, logger
@@ -94,10 +96,14 @@ def handle_state_update_task(port, fvp_dict, y_cable_presence, stopping_event):
9496
# Thread wrapper class to update ycable info periodically
9597

9698

97-
class YcableInfoUpdateTask(object):
98-
def __init__(self):
99-
self.task_thread = None
99+
100+
class YcableInfoUpdateTask(threading.Thread):
101+
102+
def __init__(self, y_cable_presence):
103+
threading.Thread.__init__(self)
104+
self.exc = None
100105
self.task_stopping_event = threading.Event()
106+
self.y_cable_presence = y_cable_presence
101107
self.table_helper = y_cable_table_helper.YcableInfoUpdateTableHelper()
102108

103109

@@ -122,25 +128,36 @@ def task_worker(self, y_cable_presence):
122128

123129
helper_logger.log_info("Stop DOM monitoring loop")
124130

125-
def task_run(self, y_cable_presence):
131+
def run(self):
126132
if self.task_stopping_event.is_set():
127133
return
128134

129-
self.task_thread = threading.Thread(target=self.task_worker, args=(y_cable_presence,))
130-
self.task_thread.start()
135+
try:
136+
self.task_worker(self.y_cable_presence)
137+
except Exception as e:
138+
helper_logger.log_error("Exception occured at child thread YcableInfoUpdateTask due to {} {}".format(repr(e), traceback.format_exc()))
139+
140+
self.exc = e
131141

132-
def task_stop(self):
133-
self.task_stopping_event.set()
134-
self.task_thread.join()
142+
def join(self):
143+
threading.Thread.join(self)
144+
145+
if self.exc:
146+
raise self.exc
135147

136148
# Process wrapper class to update sfp state info periodically
137149

138150

139-
class YcableStateUpdateTask(object):
140-
def __init__(self):
141-
self.task_process = None
151+
152+
153+
class YcableStateUpdateTask(threading.Thread):
154+
def __init__(self, sfp_error_event, y_cable_presence):
155+
threading.Thread.__init__(self)
156+
self.exc = None
142157
self.task_stopping_event = threading.Event()
143158
self.sfp_insert_events = {}
159+
self.sfp_error_event = sfp_error_event
160+
self.y_cable_presence = y_cable_presence
144161
self.table_helper = y_cable_table_helper.YcableStateUpdateTableHelper()
145162

146163

@@ -192,18 +209,21 @@ def task_worker(self, stopping_event, sfp_error_event, y_cable_presence):
192209

193210
handle_state_update_task(port, fvp_dict, y_cable_presence, stopping_event)
194211

195-
196-
def task_run(self, sfp_error_event, y_cable_presence):
212+
def run(self):
197213
if self.task_stopping_event.is_set():
198214
return
199215

200-
self.task_process = threading.Thread(target=self.task_worker, args=(
201-
self.task_stopping_event, sfp_error_event, y_cable_presence))
202-
self.task_process.start()
216+
try:
217+
self.task_worker(self.task_stopping_event, self.sfp_error_event, self.y_cable_presence)
218+
except Exception as e:
219+
helper_logger.log_error("Exception occured at child thread YcableStateUpdateTask due to {} {}".format(repr(e), traceback.format_exc()))
220+
self.exc = e
221+
222+
def join(self):
223+
threading.Thread.join(self)
203224

204-
def task_stop(self):
205-
self.task_stopping_event.set()
206-
self.task_process.join()
225+
if self.exc:
226+
raise self.exc
207227

208228
#
209229
# Daemon =======================================================================
@@ -220,6 +240,7 @@ def __init__(self, log_identifier):
220240
self.sfp_error_event = threading.Event()
221241
self.y_cable_presence = [False]
222242
self.table_helper = y_cable_table_helper.DaemonYcableTableHelper()
243+
self.threads = []
223244

224245
# Signal handler
225246
def signal_handler(self, sig, frame):
@@ -349,36 +370,58 @@ def run(self):
349370
self.init()
350371

351372
# Start the ycable task update thread
352-
ycable_info_update = YcableInfoUpdateTask()
353-
ycable_info_update.task_run(self.y_cable_presence)
373+
ycable_info_update = YcableInfoUpdateTask(self.y_cable_presence)
374+
ycable_info_update.start()
375+
self.threads.append(ycable_info_update)
354376

355377
# Start the sfp state info update process
356-
ycable_state_update = YcableStateUpdateTask()
357-
ycable_state_update.task_run(self.sfp_error_event, self.y_cable_presence)
378+
ycable_state_update = YcableStateUpdateTask(self.sfp_error_event, self.y_cable_presence)
379+
ycable_state_update.start()
380+
self.threads.append(ycable_state_update)
358381

359382
# Start the Y-cable state info update process if Y cable presence established
360383
y_cable_state_worker_update = None
361384
if self.y_cable_presence[0] is True:
362385
y_cable_state_worker_update = y_cable_helper.YCableTableUpdateTask()
363-
y_cable_state_worker_update.task_run()
386+
y_cable_state_worker_update.start()
387+
self.threads.append(y_cable_state_worker_update)
388+
y_cable_cli_worker_update = y_cable_helper.YCableCliUpdateTask()
389+
y_cable_cli_worker_update.start()
390+
self.threads.append(y_cable_cli_worker_update)
364391

365392
# Start main loop
366393
self.log_info("Start daemon main loop")
367394

368395
while not self.stop_event.wait(self.timeout):
369396
self.log_info("Ycable main loop")
397+
# check all threads are alive
398+
for thread in self.threads:
399+
if thread.is_alive() is False:
400+
try:
401+
thread.join()
402+
except Exception as e:
403+
self.log_error("Exception occured at child thread {} to {}".format(thread.getName(), repr(e)))
404+
self.log_error("thread id {} is not running, exiting main loop".format(thread.getName()))
405+
os.kill(os.getpid(), signal.SIGKILL)
370406

371-
self.log_info("Stop daemon main loop")
407+
408+
self.log_error("Stop daemon main loop")
372409

373410
# Stop the ycable periodic info info update thread
374-
ycable_info_update.task_stop()
411+
if ycable_info_update.is_alive():
412+
ycable_info_update.join()
375413

376414
# Stop the ycable update process
377-
ycable_state_update.task_stop()
415+
if ycable_state_update.is_alive():
416+
ycable_state_update.join()
378417

379418
# Stop the Y-cable state info update process
380419
if self.y_cable_presence[0] is True:
381-
y_cable_state_worker_update.task_stop()
420+
if y_cable_state_worker_update.is_alive():
421+
y_cable_state_worker_update.join()
422+
if y_cable_cli_worker_update.is_alive():
423+
y_cable_cli_worker_update.join()
424+
382425

383426
# Start daemon deinitialization sequence
384427
self.deinit()

0 commit comments

Comments
 (0)