Skip to content

Commit 1631734

Browse files
committed
fix: improve Cinema 4D license error handling
Signed-off-by: Karthik Bekal Pattathana <133984042+karthikbekalp@users.noreply.github.com>
1 parent 0364064 commit 1631734

2 files changed

Lines changed: 92 additions & 11 deletions

File tree

  • src/deadline/cinema4d_adaptor/Cinema4DAdaptor
  • test/unit/deadline_adaptor_for_cinema4d/Cinema4DAdaptor

src/deadline/cinema4d_adaptor/Cinema4DAdaptor/adaptor.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,8 @@ def _get_regex_callbacks(self) -> list[RegexCallback]:
275275
re.compile(r".*Rendering failed.*", re.IGNORECASE),
276276
re.compile(r".*Asset missing.*", re.IGNORECASE),
277277
re.compile(r".*Asset Error.*", re.IGNORECASE),
278-
re.compile(r".*Invalid License.*", re.IGNORECASE),
279-
re.compile(r".*licensing error.*", re.IGNORECASE),
280-
re.compile(r".*License Check error.*", re.IGNORECASE),
281278
re.compile(r".*Files cannot be written.*", re.IGNORECASE),
282-
re.compile(r".*Enter Registration Data.*", re.IGNORECASE),
283279
re.compile(r".*Unable to write file.*", re.IGNORECASE),
284-
re.compile(r".*\[rlm\] abort_on_license_fail enabled.*", re.IGNORECASE),
285280
re.compile(r".*RenderDocument failed with return code.*", re.IGNORECASE),
286281
re.compile(r".*Frame rendering aborted.*", re.IGNORECASE),
287282
re.compile(r".*Rendering was internally aborted.*", re.IGNORECASE),
@@ -314,6 +309,24 @@ def _get_regex_callbacks(self) -> list[RegexCallback]:
314309
)
315310
)
316311

312+
# License failures cannot recover non-interactively and would otherwise
313+
# consume worker time until the initialization timeout.
314+
license_error_regexes = [
315+
re.compile(r".*Invalid License.*", re.IGNORECASE),
316+
re.compile(r".*licensing error.*", re.IGNORECASE),
317+
re.compile(r".*License Check error.*", re.IGNORECASE),
318+
re.compile(r".*Enter Registration Data.*", re.IGNORECASE),
319+
re.compile(r".*\[rlm\] abort_on_license_fail enabled.*", re.IGNORECASE),
320+
re.compile(r".*No license found.*", re.IGNORECASE),
321+
re.compile(r".*No available licenses to choose.*", re.IGNORECASE),
322+
]
323+
callback_list.append(
324+
RegexCallback(
325+
license_error_regexes,
326+
self._handle_license_error,
327+
)
328+
)
329+
317330
self._regex_callbacks = callback_list
318331
return self._regex_callbacks
319332

@@ -404,6 +417,19 @@ def _handle_nvidia_driver_error(self, match: re.Match) -> None:
404417
)
405418
self._exc_info = RuntimeError(message)
406419

420+
def _handle_license_error(self, match: re.Match) -> None:
421+
"""Handle a fatal Cinema 4D licensing failure."""
422+
message = (
423+
"Cinema 4D failed to acquire a license.\n"
424+
"If you are using bring your own license (BYOL), check your license configuration "
425+
"and availability.\n"
426+
"If you are using usage-based licensing (UBL) from AWS Deadline Cloud and need a "
427+
"higher 'License sessions per license endpoint' limit, contact the AWS Deadline Cloud "
428+
"team to request an increase.\n"
429+
f"Error: {match.group(0)}"
430+
)
431+
self._exc_info = RuntimeError(message)
432+
407433
def _add_deadline_openjd_paths(self) -> None:
408434
# Add the openjd namespace directory to PYTHONPATH, so that adaptor_runtime_client
409435
# will be available directly to the adaptor client.

test/unit/deadline_adaptor_for_cinema4d/Cinema4DAdaptor/test_adaptor.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# the same Cinema4DAdaptor action queue, which can cause race conditions and
77
# test failures when tests run in parallel across multiple workers.
88
import json
9+
import logging
910
from pathlib import Path
1011
from unittest.mock import Mock, PropertyMock, patch
1112

@@ -71,8 +72,6 @@ class TestCinema4DAdaptor_errors_on_cleanup:
7172
[
7273
# Critical stops should not fail the job.
7374
("CRITICAL: Stop [ge_file.cpp(1172)]", False),
74-
# Any string with substring "Error:" should fail the job
75-
("Redshift Error: Maxon licensing error: User not logged in (7)", True),
7675
# This error can be printed but the jobs are still successful.
7776
# Hence, this should not fail the job.
7877
("CRITICAL: nullptr [text_object.cpp(1082)] [objectbase1.hxx(549)]", False),
@@ -84,12 +83,8 @@ class TestCinema4DAdaptor_errors_on_cleanup:
8483
("Rendering failed", True),
8584
("Asset missing", True),
8685
("Asset Error", True),
87-
("Invalid License", True),
88-
("License Check error", True),
8986
("Files cannot be written", True),
90-
("Enter Registration Data", True),
9187
("Unable to write file", True),
92-
("[rlm] abort_on_license_fail enabled", True),
9388
("RenderDocument failed with return code", True),
9489
("Frame rendering aborted", True),
9590
("Rendering was internally aborted", True),
@@ -364,6 +359,66 @@ def test__wait_for_socket(
364359
# THEN
365360
assert mock_sleep.call_count == 3
366361

362+
@pytest.mark.parametrize(
363+
"license_error",
364+
[
365+
"Redshift Error: Maxon licensing error: User not logged in (7)",
366+
"Invalid License",
367+
"License Check error",
368+
"Enter Registration Data",
369+
"[rlm] abort_on_license_fail enabled",
370+
"17:44:34 No license found",
371+
"18:38:54 No available licenses to choose",
372+
],
373+
)
374+
def test_license_failure_from_stdout_interrupts_startup(
375+
self, init_data: dict, license_error: str
376+
) -> None:
377+
"""Tests that a licensing prompt fails startup without waiting for the timeout."""
378+
# General error checking is optional, but a licensing prompt cannot recover
379+
# without interactive input and must always stop the worker.
380+
init_data["activate_error_checking"] = "0"
381+
adaptor = Cinema4DAdaptor(init_data)
382+
expected_error = (
383+
"Cinema 4D failed to acquire a license.\n"
384+
"If you are using bring your own license (BYOL), check your license configuration "
385+
"and availability.\n"
386+
"If you are using usage-based licensing (UBL) from AWS Deadline Cloud and need a "
387+
"higher 'License sessions per license endpoint' limit, contact the AWS Deadline Cloud "
388+
"team to request an increase.\n"
389+
f"Error: {license_error}"
390+
)
391+
392+
def emit_license_error(*args, **kwargs):
393+
kwargs["stdout_handler"].emit(
394+
logging.LogRecord(
395+
name="cinema4d",
396+
level=logging.ERROR,
397+
pathname="",
398+
lineno=0,
399+
msg=license_error,
400+
args=(),
401+
exc_info=None,
402+
)
403+
)
404+
process = Mock()
405+
process.is_running = True
406+
return process
407+
408+
with (
409+
patch.object(adaptor, "_initialize_maxon_assets_db_connection"),
410+
patch.object(adaptor, "_start_cinema4d_server_thread"),
411+
patch.object(adaptor, "_populate_action_queue"),
412+
patch(
413+
"deadline.cinema4d_adaptor.Cinema4DAdaptor.adaptor.LoggingSubprocess",
414+
side_effect=emit_license_error,
415+
),
416+
pytest.raises(RuntimeError) as exc_info,
417+
):
418+
adaptor.on_start()
419+
420+
assert str(exc_info.value) == expected_error
421+
367422

368423
@pytest.mark.xdist_group(name="adaptor_tests")
369424
class TestCinema4DAdaptor_on_run:

0 commit comments

Comments
 (0)