Skip to content

Commit 7896085

Browse files
feat: avoiding reconnecting if MAPDL exited already (#3708)
* test: refactor check_stds and post_mortem_checks * test: backing off algorithm * chore: adding changelog file 3703.added.md [dependabot-skip] * fix: codacity warnings * feat: using get_value to obtain the n elements * revert: revert "feat: using get_value to obtain the n elements" Performance is not as go This reverts commit 877f803. * feat: using get_value to obtain the n elements * revert: revert "feat: using get_value to obtain the n elements" Performance is not as go This reverts commit 877f803. * feat: using mapdl.exit when raising final error. * test: fix test by avoiding killing mapdl. * fix: test * fix: test * feat: adding warnings when restarting MAPDL during testing * fix: test * feat: caching requires_package * test: adding more tests * chore: adding changelog file 3705.added.md [dependabot-skip] * chore: adding changelog file 3705.added.md [dependabot-skip] * fix: warnings import * feat: not reconnecting if MAPDL already exited * test: adding tests * chore: adding changelog file 3708.miscellaneous.md [dependabot-skip] * fix: tests --------- Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent b833c3c commit 7896085

File tree

4 files changed

+207
-15
lines changed

4 files changed

+207
-15
lines changed

doc/changelog.d/3708.miscellaneous.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat: avoiding reconnecting if MAPDL exited already

src/ansys/mapdl/core/errors.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -323,25 +323,29 @@ def wrapper(*args, **kwargs):
323323
except grpc.RpcError as error:
324324

325325
mapdl = retrieve_mapdl_from_args(args)
326+
326327
mapdl._log.debug("A gRPC error has been detected.")
327328

328-
i_attemps += 1
329-
if i_attemps <= n_attempts:
329+
if not mapdl.exited:
330+
i_attemps += 1
331+
if i_attemps <= n_attempts:
330332

331-
wait = (
332-
initial_backoff * multiplier_backoff**i_attemps
333-
) # Exponential backoff
334-
sleep(wait)
333+
wait = (
334+
initial_backoff * multiplier_backoff**i_attemps
335+
) # Exponential backoff
335336

336-
# reconnect
337-
mapdl._log.debug(
338-
f"Re-connection attempt {i_attemps} after waiting {wait:0.3f} seconds"
339-
)
337+
# reconnect
338+
mapdl._log.debug(
339+
f"Re-connection attempt {i_attemps} after waiting {wait:0.3f} seconds"
340+
)
340341

341-
connected = mapdl._connect(timeout=wait)
342+
if not mapdl.is_alive:
343+
connected = mapdl._connect(timeout=wait)
344+
else:
345+
sleep(wait)
342346

343-
# Retry again
344-
continue
347+
# Retry again
348+
continue
345349

346350
# Custom errors
347351
reason = ""

tests/test_cli.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,79 @@ def test_launch_mapdl_cli(monkeypatch, run_cli, start_instance):
7676
# grab ips and port
7777
pid = int(re.search(r"\(PID=(\d+)\)", output).groups()[0])
7878

79-
output = run_cli(f"stop --port {PORT1}")
80-
assert "success" in output.lower()
79+
80+
@requires("click")
81+
@pytest.mark.parametrize(
82+
"mapping",
83+
((False, True), (False, True, False), (False, False, False), (True, True, False)),
84+
)
85+
def test_pymapdl_stop_instances(run_cli, mapping):
86+
87+
fake_process_ = [
88+
{
89+
"pid": np.random.randint(10000, 100000),
90+
"name": f"ansys251_{ind}" if each else "process",
91+
"port": str(50052 + ind),
92+
"ansys_process": each,
93+
}
94+
for ind, each in enumerate(mapping)
95+
]
96+
97+
fake_processes = [make_fake_process(**each) for each in fake_process_]
98+
99+
with (
100+
patch("ansys.mapdl.core.cli.stop._kill_process") as mock_kill,
101+
patch("psutil.pid_exists") as mock_pid,
102+
patch("psutil.process_iter", return_value=iter(fake_processes)),
103+
):
104+
105+
mock_pid.return_value = lambda *args, **kwargs: True # All process exists
106+
mock_kill.side_effect = lambda *args, **kwargs: None # avoid kill nothing
107+
108+
if sum(mapping) == 0:
109+
output = run_cli(f"stop --port {PORT1}")
110+
assert (
111+
f"error: no ansys instances running on port {PORT1}" in output.lower()
112+
)
113+
mock_kill.assert_not_called()
114+
115+
output = run_cli(f"stop --all")
116+
assert f"error: no ansys instances have been found." in output.lower()
117+
mock_kill.assert_not_called()
118+
119+
elif sum(mapping) == 1:
120+
# Port
121+
process, process_mock = [
122+
(each, each_mock)
123+
for each, each_mock in zip(fake_process_, fake_processes)
124+
if "ansys251" in each["name"]
125+
][0]
126+
port = process["port"]
127+
128+
output = run_cli(f"stop --port {port}")
129+
assert (
130+
f"success: ansys instances running on port {port} have been stopped"
131+
in output.lower()
132+
)
133+
134+
# PID
135+
pid = process["pid"]
136+
with patch("psutil.Process") as mock_process:
137+
mock_process.return_value = process_mock
138+
139+
output = run_cli(f"stop --pid {pid}")
140+
assert (
141+
f"the process with pid {pid} and its children have been stopped."
142+
in output.lower()
143+
)
144+
145+
mock_kill.assert_called()
146+
assert mock_kill.call_count == 2
147+
148+
else:
149+
output = run_cli(f"stop --all")
150+
assert "success: ansys instances have been stopped." in output.lower()
151+
assert mock_kill.call_count == sum(mapping)
81152

82153

83154
@requires("click")

tests/test_mapdl.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2599,3 +2599,119 @@ def test_comment_on_debug_mode(mapdl, cleared):
25992599
mockcom.assert_called_once_with("Entering in non_interactive mode")
26002600

26012601
mapdl.logger.logger.level = loglevel
2602+
2603+
2604+
@patch("ansys.mapdl.core.errors.N_ATTEMPTS", 2)
2605+
@patch("ansys.mapdl.core.errors.MULTIPLIER_BACKOFF", 1)
2606+
@pytest.mark.parametrize("is_exited", [True, False])
2607+
def test_timeout_when_exiting(mapdl, is_exited):
2608+
from ansys.mapdl.core import errors
2609+
2610+
def raise_exception(*args, **kwargs):
2611+
from grpc import RpcError
2612+
2613+
e = RpcError("My patched error")
2614+
e.code = lambda: grpc.StatusCode.ABORTED
2615+
e.details = lambda: "My gRPC error details"
2616+
2617+
# Simulating MAPDL exiting by force
2618+
mapdl._exited = is_exited
2619+
2620+
raise e
2621+
2622+
handle_generic_grpc_error = errors.handle_generic_grpc_error
2623+
2624+
with (
2625+
patch("ansys.mapdl.core.mapdl_grpc.pb_types.CmdRequest") as mock_cmdrequest,
2626+
patch(
2627+
"ansys.mapdl.core.mapdl_grpc.MapdlGrpc.is_alive", new_callable=PropertyMock
2628+
) as mock_is_alive,
2629+
patch.object(mapdl, "_connect") as mock_connect,
2630+
patch(
2631+
"ansys.mapdl.core.errors.handle_generic_grpc_error", autospec=True
2632+
) as mock_handle,
2633+
patch.object(mapdl, "_exit_mapdl") as mock_exit_mapdl,
2634+
):
2635+
2636+
mock_exit_mapdl.return_value = None # Avoid exiting
2637+
mock_is_alive.return_value = False
2638+
mock_connect.return_value = None # patched to avoid timeout
2639+
mock_cmdrequest.side_effect = raise_exception
2640+
mock_handle.side_effect = handle_generic_grpc_error
2641+
2642+
with pytest.raises(MapdlExitedError):
2643+
mapdl.prep7()
2644+
2645+
# After
2646+
assert mapdl._exited
2647+
2648+
assert mock_handle.call_count == 1
2649+
2650+
if is_exited:
2651+
# Checking no trying to reconnect
2652+
assert mock_connect.call_count == 0
2653+
assert mock_cmdrequest.call_count == 1
2654+
assert mock_is_alive.call_count == 1
2655+
2656+
else:
2657+
assert mock_connect.call_count == errors.N_ATTEMPTS
2658+
assert mock_cmdrequest.call_count == errors.N_ATTEMPTS + 1
2659+
assert mock_is_alive.call_count == errors.N_ATTEMPTS + 1
2660+
2661+
mapdl._exited = False
2662+
2663+
2664+
@pytest.mark.parametrize(
2665+
"cmd,arg",
2666+
(
2667+
("block", None),
2668+
("nsel", None),
2669+
("esel", None),
2670+
("ksel", None),
2671+
("modopt", None),
2672+
),
2673+
)
2674+
def test_none_as_argument(mapdl, make_block, cmd, arg):
2675+
if "sel" in cmd:
2676+
kwargs = {"wraps": mapdl._run}
2677+
else:
2678+
kwargs = {}
2679+
2680+
with patch.object(mapdl, "_run", **kwargs) as mock_run:
2681+
2682+
mock_run.assert_not_called()
2683+
2684+
func = getattr(mapdl, cmd)
2685+
out = func(arg)
2686+
2687+
mock_run.assert_called()
2688+
2689+
if "sel" in cmd:
2690+
assert isinstance(out, np.ndarray)
2691+
assert len(out) == 0
2692+
2693+
cmd = mock_run.call_args_list[0].args[0]
2694+
assert isinstance(cmd, str)
2695+
assert "NONE" in cmd.upper()
2696+
2697+
2698+
@pytest.mark.parametrize("func", ["ksel", "lsel", "asel", "vsel"])
2699+
def test_none_on_selecting(mapdl, cleared, func):
2700+
mapdl.block(0, 1, 0, 1, 0, 1)
2701+
2702+
selfunc = getattr(mapdl, func)
2703+
2704+
assert len(selfunc("all")) > 0
2705+
assert len(selfunc(None)) == 0
2706+
2707+
2708+
@requires("pyvista")
2709+
def test_requires_package_speed():
2710+
from ansys.mapdl.core.misc import requires_package
2711+
2712+
@requires_package("pyvista")
2713+
def my_func(i):
2714+
return i + 1
2715+
2716+
for i in range(1_000_000):
2717+
my_func(i)

0 commit comments

Comments
 (0)