Skip to content

Commit 38f3af8

Browse files
ff137swcurran
andauthored
✨ Don't shutdown on ledger error (openwallet-foundation#3636)
* ✨ Don't shutdown on ledger error Signed-off-by: ff137 <[email protected]> * ✅ Fix tests Signed-off-by: ff137 <[email protected]> * 🧪 Don't skip test Signed-off-by: ff137 <[email protected]> * 🧪 Cover StorageNotFoundError scenario Signed-off-by: ff137 <[email protected]> * 🧪 Fix confusing unawaited coroutine warning Signed-off-by: ff137 <[email protected]> --------- Signed-off-by: ff137 <[email protected]> Co-authored-by: Stephen Curran <[email protected]>
1 parent b68573b commit 38f3af8

File tree

2 files changed

+33
-57
lines changed

2 files changed

+33
-57
lines changed

acapy_agent/core/conductor.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
from ..wallet.anoncreds_upgrade import upgrade_wallet_to_anoncreds_if_requested
7575
from ..wallet.did_info import DIDInfo
7676
from .dispatcher import Dispatcher
77-
from .error import StartupError
77+
from .error import ProfileError, StartupError
7878
from .oob_processor import OobMessageProcessor
7979
from .util import SHUTDOWN_EVENT_TOPIC, STARTUP_EVENT_TOPIC
8080

@@ -559,31 +559,31 @@ def inbound_message_router(
559559
lambda completed: self.dispatch_complete(message, completed),
560560
)
561561
except (LedgerConfigError, LedgerTransactionError) as e:
562-
LOGGER.error("Shutdown on ledger error %s", str(e))
563-
if self.admin_server:
564-
self.admin_server.notify_fatal_error()
562+
LOGGER.error("Ledger error occurred in message handler: %s", str(e))
565563
raise
566564

567565
def dispatch_complete(self, message: InboundMessage, completed: CompletedTask):
568566
"""Handle completion of message dispatch."""
569567
if completed.exc_info:
570-
LOGGER.exception("Exception in message handler:", exc_info=completed.exc_info)
571-
if isinstance(completed.exc_info[1], LedgerConfigError) or isinstance(
572-
completed.exc_info[1], LedgerTransactionError
573-
):
568+
exc_class, exc, _ = completed.exc_info
569+
if isinstance(exc, (LedgerConfigError, LedgerTransactionError)):
574570
LOGGER.error(
575-
"%shutdown on ledger error %s",
576-
"S" if self.admin_server else "No admin server to s",
577-
str(completed.exc_info[1]),
571+
"Ledger error occurred in message handler: %s",
572+
str(exc),
573+
exc_info=completed.exc_info,
578574
)
579-
if self.admin_server:
580-
self.admin_server.notify_fatal_error()
581-
else:
575+
elif isinstance(exc, (ProfileError, StorageNotFoundError)):
582576
LOGGER.error(
583-
"DON'T shutdown on %s %s",
584-
completed.exc_info[0].__name__,
585-
str(completed.exc_info[1]),
577+
"Storage error occurred in message handler: %s: %s",
578+
exc_class.__name__,
579+
str(exc),
580+
exc_info=completed.exc_info,
581+
)
582+
else:
583+
LOGGER.exception(
584+
"Exception in message handler:", exc_info=completed.exc_info
586585
)
586+
587587
self.inbound_transport_manager.dispatch_complete(message, completed)
588588

589589
async def get_stats(self) -> dict:
@@ -656,9 +656,9 @@ def handle_not_returned(self, profile: Profile, outbound: OutboundMessage):
656656
try:
657657
self.dispatcher.run_task(self.queue_outbound(profile, outbound))
658658
except (LedgerConfigError, LedgerTransactionError) as e:
659-
LOGGER.error("Shutdown on ledger error %s", str(e))
660-
if self.admin_server:
661-
self.admin_server.notify_fatal_error()
659+
LOGGER.error(
660+
"Ledger error occurred while handling failed delivery: %s", str(e)
661+
)
662662
raise
663663

664664
async def queue_outbound(
@@ -688,9 +688,9 @@ async def queue_outbound(
688688
LOGGER.exception("Error preparing outbound message for transmission")
689689
return OutboundSendStatus.UNDELIVERABLE
690690
except (LedgerConfigError, LedgerTransactionError) as e:
691-
LOGGER.error("Shutdown on ledger error %s", str(e))
692-
if self.admin_server:
693-
self.admin_server.notify_fatal_error()
691+
LOGGER.error(
692+
"Ledger error occurred while preparing outbound message: %s", str(e)
693+
)
694694
raise
695695
del conn_mgr
696696
# Find oob/connectionless target we can send the message to

acapy_agent/core/tests/test_conductor.py

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
from unittest import IsolatedAsyncioTestCase
22

3-
import pytest
4-
5-
from ...connections.base_manager import BaseConnectionManager
63
from ...admin.base_server import BaseAdminServer
74
from ...askar.profile import AskarProfileManager
85
from ...config.base_context import ContextBuilder
96
from ...config.injection_context import InjectionContext
7+
from ...connections.base_manager import BaseConnectionManager
108
from ...connections.models.conn_record import ConnRecord
119
from ...connections.models.connection_target import ConnectionTarget
1210
from ...connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service
@@ -587,9 +585,7 @@ async def test_inbound_message_handler_ledger_x(self):
587585
mock.patch.object(
588586
conductor.dispatcher, "queue_message", autospec=True
589587
) as mock_dispatch_q,
590-
mock.patch.object(
591-
conductor.admin_server, "notify_fatal_error", mock.MagicMock()
592-
) as mock_notify,
588+
mock.patch.object(test_module, "LOGGER", mock.MagicMock()) as mock_logger,
593589
):
594590
mock_dispatch_q.side_effect = test_module.LedgerConfigError("ledger down")
595591

@@ -603,7 +599,7 @@ async def test_inbound_message_handler_ledger_x(self):
603599
)
604600

605601
mock_dispatch_q.assert_called_once()
606-
mock_notify.assert_called_once()
602+
mock_logger.error.assert_called_once()
607603

608604
async def test_outbound_message_handler_return_route(self):
609605
builder: ContextBuilder = StubContextBuilder(self.test_settings)
@@ -830,18 +826,10 @@ async def test_handle_nots(self):
830826
conductor.dispatcher, "run_task", mock.MagicMock()
831827
) as mock_run_task,
832828
):
833-
# Normally this should be a coroutine mock; however, the coroutine
834-
# is awaited by dispatcher.run_task, which is mocked here. MagicMock
835-
# to prevent unawaited coroutine warning.
836-
mock_conn_mgr.return_value.get_connection_targets = mock.MagicMock()
837829
mock_run_task.side_effect = test_module.BaseConnectionManagerError()
838830
await conductor.queue_outbound(conductor.root_profile, message)
839-
mock_outbound_mgr.return_value.enqueue_message.assert_not_called()
840831

841832
message.connection_id = None
842-
mock_outbound_mgr.return_value.enqueue_message.side_effect = (
843-
test_module.OutboundDeliveryError()
844-
)
845833
await conductor.queue_outbound(conductor.root_profile, message)
846834
mock_run_task.assert_called_once()
847835

@@ -870,7 +858,6 @@ async def test_handle_outbound_queue(self):
870858

871859
await conductor.queue_outbound(conductor.root_profile, message)
872860

873-
@pytest.mark.skip("This test has a bad mock that isn't awaited")
874861
async def test_handle_not_returned_ledger_x(self):
875862
builder: ContextBuilder = StubContextBuilder(self.test_settings_admin)
876863
conductor = test_module.Conductor(builder)
@@ -898,9 +885,7 @@ async def test_handle_not_returned_ledger_x(self):
898885
mock.patch.object(
899886
conductor.dispatcher, "run_task", mock.MagicMock()
900887
) as mock_dispatch_run,
901-
mock.patch.object(
902-
conductor.admin_server, "notify_fatal_error", mock.MagicMock()
903-
) as mock_notify,
888+
mock.patch.object(conductor, "queue_outbound", mock.MagicMock()),
904889
):
905890
mock_dispatch_run.side_effect = test_module.LedgerConfigError(
906891
"No such ledger"
@@ -917,7 +902,6 @@ async def test_handle_not_returned_ledger_x(self):
917902
conductor.handle_not_returned(conductor.root_profile, message)
918903

919904
mock_dispatch_run.assert_called_once()
920-
mock_notify.assert_called_once()
921905

922906
async def test_queue_outbound_ledger_x(self):
923907
builder: ContextBuilder = StubContextBuilder(self.test_settings_admin)
@@ -949,14 +933,8 @@ async def test_queue_outbound_ledger_x(self):
949933
mock.patch.object(
950934
conductor.dispatcher, "run_task", mock.MagicMock()
951935
) as mock_dispatch_run,
952-
mock.patch.object(
953-
conductor.admin_server, "notify_fatal_error", mock.MagicMock()
954-
) as mock_notify,
936+
mock.patch.object(test_module, "LOGGER", mock.MagicMock()) as mock_logger,
955937
):
956-
# Normally this should be a coroutine mock; however, the coroutine
957-
# is awaited by dispatcher.run_task, which is mocked here. MagicMock
958-
# to prevent unawaited coroutine warning.
959-
conn_mgr.get_connection_targets = mock.MagicMock()
960938
mock_dispatch_run.side_effect = test_module.LedgerConfigError(
961939
"No such ledger"
962940
)
@@ -972,7 +950,7 @@ async def test_queue_outbound_ledger_x(self):
972950
await conductor.queue_outbound(conductor.root_profile, message)
973951

974952
mock_dispatch_run.assert_called_once()
975-
mock_notify.assert_called_once()
953+
mock_logger.error.assert_called_once()
976954

977955
async def test_admin(self):
978956
builder: ContextBuilder = StubContextBuilder(self.test_settings)
@@ -1217,7 +1195,7 @@ async def test_dispatch_complete_non_fatal_x(self):
12171195
message_body = "{}"
12181196
receipt = MessageReceipt(direct_response_mode="snail mail")
12191197
message = InboundMessage(message_body, receipt)
1220-
exc = KeyError("sample exception")
1198+
exc = StorageNotFoundError("sample exception")
12211199
mock_task = mock.MagicMock(
12221200
exc_info=(type(exc), exc, exc.__traceback__),
12231201
ident="abc",
@@ -1255,7 +1233,7 @@ async def test_dispatch_complete_non_fatal_x(self):
12551233
conductor.dispatch_complete(message, mock_task)
12561234
mock_notify.assert_not_called()
12571235

1258-
async def test_dispatch_complete_fatal_x(self):
1236+
async def test_dispatch_complete_ledger_error_x(self):
12591237
builder: ContextBuilder = StubContextBuilder(self.test_settings_admin)
12601238
conductor = test_module.Conductor(builder)
12611239

@@ -1294,11 +1272,9 @@ async def test_dispatch_complete_fatal_x(self):
12941272
}
12951273
await conductor.setup()
12961274

1297-
with mock.patch.object(
1298-
conductor.admin_server, "notify_fatal_error", mock.MagicMock()
1299-
) as mock_notify:
1275+
with mock.patch.object(test_module, "LOGGER", mock.MagicMock()) as mock_logger:
13001276
conductor.dispatch_complete(message, mock_task)
1301-
mock_notify.assert_called_once_with()
1277+
mock_logger.error.assert_called_once()
13021278

13031279
async def test_clear_default_mediator(self):
13041280
builder: ContextBuilder = StubContextBuilder(self.test_settings)

0 commit comments

Comments
 (0)