Skip to content

Commit

Permalink
🔧 increase publish-revocations timeout and make configurable (#987)
Browse files Browse the repository at this point in the history
* ✅ increase test sleep duration

* 🔧 make publish revocations timeout configurable
  • Loading branch information
ff137 authored Dec 11, 2024
1 parent e190dce commit 65b97a1
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 3 deletions.
6 changes: 5 additions & 1 deletion app/routes/revocation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import os
from typing import Optional

from aries_cloudcontroller import IssuerCredRevRecord, RevRegWalletUpdatedResult
Expand All @@ -23,6 +24,9 @@

router = APIRouter(prefix="/v1/issuer/credentials", tags=["revocation"])

# Config for /publish-revocations
publish_revocations_timeout = int(os.getenv("PUBLISH_REVOCATIONS_TIMEOUT", "60"))


@router.post("/revoke", summary="Revoke a Credential (if revocable)")
async def revoke_credential(
Expand Down Expand Up @@ -209,7 +213,7 @@ async def publish_revocations(
field_name="state",
expected_value="transaction_acked",
logger=bound_logger,
max_attempts=30,
max_attempts=publish_revocations_timeout,
retry_delay=1,
)
except asyncio.TimeoutError as e:
Expand Down
5 changes: 3 additions & 2 deletions app/tests/e2e/verifier/test_proof_revoked_credential.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio

Check failure on line 1 in app/tests/e2e/verifier/test_proof_revoked_credential.py

View workflow job for this annotation

GitHub Actions / JUnit Test Report

test_proof_revoked_credential.test_proof_revoked_credential[clean-clean-clean-clean-clean-clean-auto_publish_true]

failed on setup with "app.exceptions.cloudapi_exception.CloudApiException: 504: Timeout waiting for revocation registry creation."
Raw output
fut = <coroutine object wait_for_active_registry at 0x7f823df76c00>
timeout = 120

    async def wait_for(fut, timeout):
        """Wait for the single Future or coroutine to complete, with timeout.
    
        Coroutine will be wrapped in Task.
    
        Returns result of the Future or coroutine.  When a timeout occurs,
        it cancels the task and raises TimeoutError.  To avoid the task
        cancellation, wrap it in shield().
    
        If the wait is cancelled, the task is also cancelled.
    
        If the task suppresses the cancellation and returns a value instead,
        that value is returned.
    
        This function is a coroutine.
        """
        # The special case for timeout <= 0 is for the following case:
        #
        # async def test_waitfor():
        #     func_started = False
        #
        #     async def func():
        #         nonlocal func_started
        #         func_started = True
        #
        #     try:
        #         await asyncio.wait_for(func(), 0)
        #     except asyncio.TimeoutError:
        #         assert not func_started
        #     else:
        #         assert False
        #
        # asyncio.run(test_waitfor())
    
    
        if timeout is not None and timeout <= 0:
            fut = ensure_future(fut)
    
            if fut.done():
                return fut.result()
    
            await _cancel_and_wait(fut)
            try:
                return fut.result()
            except exceptions.CancelledError as exc:
                raise TimeoutError from exc
    
        async with timeouts.timeout(timeout):
>           return await fut

/usr/local/lib/python3.12/asyncio/tasks.py:520: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
app/services/revocation_registry.py:489: in wait_for_active_registry
    await asyncio.sleep(sleep_duration)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

delay = 0.5, result = None

    async def sleep(delay, result=None):
        """Coroutine that completes after a given time (in seconds)."""
        if delay <= 0:
            await __sleep0()
            return result
    
        loop = events.get_running_loop()
        future = loop.create_future()
        h = loop.call_later(delay,
                            futures._set_result_unless_cancelled,
                            future, result)
        try:
>           return await future
E           asyncio.exceptions.CancelledError

/usr/local/lib/python3.12/asyncio/tasks.py:665: CancelledError

The above exception was the direct cause of the following exception:

self = <app.services.definitions.credential_definition_publisher.CredentialDefinitionPublisher object at 0x7f823d7fb740>
credential_definition_id = 'JE6gMeAthoEgJyzv71a7yq:3:CL:3522:tag'

    async def wait_for_revocation_registry(self, credential_definition_id):
        try:
            self._logger.debug("Waiting for revocation registry creation")
>           await asyncio.wait_for(
                wait_for_active_registry(self._controller, credential_definition_id),
                timeout=REGISTRY_CREATION_TIMEOUT,
            )

app/services/definitions/credential_definition_publisher.py:65: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.12/asyncio/tasks.py:519: in wait_for
    async with timeouts.timeout(timeout):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Timeout [expired]>
exc_type = <class 'asyncio.exceptions.CancelledError'>
exc_val = CancelledError(), exc_tb = <traceback object at 0x7f823e382fc0>

    async def __aexit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> Optional[bool]:
        assert self._state in (_State.ENTERED, _State.EXPIRING)
    
        if self._timeout_handler is not None:
            self._timeout_handler.cancel()
            self._timeout_handler = None
    
        if self._state is _State.EXPIRING:
            self._state = _State.EXPIRED
    
            if self._task.uncancel() <= self._cancelling and exc_type is exceptions.CancelledError:
                # Since there are no new cancel requests, we're
                # handling this.
>               raise TimeoutError from exc_val
E               TimeoutError

/usr/local/lib/python3.12/asyncio/timeouts.py:115: TimeoutError

The above exception was the direct cause of the following exception:

anyio_backend = 'asyncio'
request = <SubRequest 'credential_definition_id_revocable' for <Function test_proof_revoked_credential[clean-clean-clean-clean-clean-clean-auto_publish_true]>>
args = ()
kwargs = {'faber_client': <shared.util.rich_async_client.RichAsyncClient object at 0x7f823e374b30>, 'request': <SubRequest 'cre...JJm:2:test_schema_alt:64.43.29', name='test_schema_alt', version='64.43.29', attribute_names=['speed', 'age', 'name'])}
local_func = <function credential_definition_id_revocable at 0x7f8245887560>
backend_name = 'asyncio', backend_options = {}
runner = <anyio._backends._asyncio.TestRunner object at 0x7f823eb731a0>

    def wrapper(
        *args: Any, anyio_backend: Any, request: SubRequest, **kwargs: Any
    ) -> Any:
        # Rebind any fixture methods to the request instance
        if (
            request.instance
            and ismethod(func)
            and type(func.__self__) is type(request.instance)
        ):
            local_func = func.__func__.__get__(request.instance)
        else:
            local_func = func
    
        backend_name, backend_options = extract_backend_and_options(anyio_backend)
        if has_backend_arg:
            kwargs["anyio_backend"] = anyio_backend
    
        if has_request_arg:
            kwargs["request"] = request
    
        with get_runner(backend_name, backend_options) as runner:
            if isasyncgenfunction(local_func):
                yield from runner.run_asyncgen_fixture(local_func, kwargs)
            else:
>               yield runner.run_fixture(local_func, kwargs)

/usr/local/lib/python3.12/site-packages/anyio/pytest_plugin.py:100: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2300: in run_fixture
    retval = self.get_loop().run_until_complete(
/usr/local/lib/python3.12/asyncio/base_events.py:686: in run_until_complete
    return future.result()
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2270: in _call_in_runner_task
    return await future
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2237: in _run_tests_and_fixtures
    retval = await coro
app/tests/fixtures/definitions.py:177: in credential_definition_id_revocable
    result = await get_clean_or_regression_test_cred_def(
app/tests/fixtures/definitions.py:139: in get_clean_or_regression_test_cred_def
    result = await create_credential_definition(
app/routes/definitions.py:255: in create_credential_definition
    credential_definition_id = await cred_def_service.create_credential_definition(
app/services/definitions/credential_definitions.py:65: in create_credential_definition
    await publisher.wait_for_revocation_registry(credential_definition_id)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <app.services.definitions.credential_definition_publisher.CredentialDefinitionPublisher object at 0x7f823d7fb740>
credential_definition_id = 'JE6gMeAthoEgJyzv71a7yq:3:CL:3522:tag'

    async def wait_for_revocation_registry(self, credential_definition_id):
        try:
            self._logger.debug("Waiting for revocation registry creation")
            await asyncio.wait_for(
                wait_for_active_registry(self._controller, credential_definition_id),
                timeout=REGISTRY_CREATION_TIMEOUT,
            )
        except asyncio.TimeoutError as e:
            self._logger.error("Timeout waiting for revocation registry creation.")
>           raise CloudApiException(
                "Timeout waiting for revocation registry creation.",
                504,
            ) from e
E           app.exceptions.cloudapi_exception.CloudApiException: 504: Timeout waiting for revocation registry creation.

app/services/definitions/credential_definition_publisher.py:71: CloudApiException

Check failure on line 1 in app/tests/e2e/verifier/test_proof_revoked_credential.py

View workflow job for this annotation

GitHub Actions / JUnit Test Report

test_proof_revoked_credential.test_proof_revoked_credential[clean-clean-clean-clean-clean-clean-default]

failed on setup with "app.exceptions.cloudapi_exception.CloudApiException: 504: Timeout waiting for revocation registry creation."
Raw output
fut = <coroutine object wait_for_active_registry at 0x7f823df76c00>
timeout = 120

    async def wait_for(fut, timeout):
        """Wait for the single Future or coroutine to complete, with timeout.
    
        Coroutine will be wrapped in Task.
    
        Returns result of the Future or coroutine.  When a timeout occurs,
        it cancels the task and raises TimeoutError.  To avoid the task
        cancellation, wrap it in shield().
    
        If the wait is cancelled, the task is also cancelled.
    
        If the task suppresses the cancellation and returns a value instead,
        that value is returned.
    
        This function is a coroutine.
        """
        # The special case for timeout <= 0 is for the following case:
        #
        # async def test_waitfor():
        #     func_started = False
        #
        #     async def func():
        #         nonlocal func_started
        #         func_started = True
        #
        #     try:
        #         await asyncio.wait_for(func(), 0)
        #     except asyncio.TimeoutError:
        #         assert not func_started
        #     else:
        #         assert False
        #
        # asyncio.run(test_waitfor())
    
    
        if timeout is not None and timeout <= 0:
            fut = ensure_future(fut)
    
            if fut.done():
                return fut.result()
    
            await _cancel_and_wait(fut)
            try:
                return fut.result()
            except exceptions.CancelledError as exc:
                raise TimeoutError from exc
    
        async with timeouts.timeout(timeout):
>           return await fut

/usr/local/lib/python3.12/asyncio/tasks.py:520: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
app/services/revocation_registry.py:489: in wait_for_active_registry
    await asyncio.sleep(sleep_duration)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

delay = 0.5, result = None

    async def sleep(delay, result=None):
        """Coroutine that completes after a given time (in seconds)."""
        if delay <= 0:
            await __sleep0()
            return result
    
        loop = events.get_running_loop()
        future = loop.create_future()
        h = loop.call_later(delay,
                            futures._set_result_unless_cancelled,
                            future, result)
        try:
>           return await future
E           asyncio.exceptions.CancelledError

/usr/local/lib/python3.12/asyncio/tasks.py:665: CancelledError

The above exception was the direct cause of the following exception:

self = <app.services.definitions.credential_definition_publisher.CredentialDefinitionPublisher object at 0x7f823d7fb740>
credential_definition_id = 'JE6gMeAthoEgJyzv71a7yq:3:CL:3522:tag'

    async def wait_for_revocation_registry(self, credential_definition_id):
        try:
            self._logger.debug("Waiting for revocation registry creation")
>           await asyncio.wait_for(
                wait_for_active_registry(self._controller, credential_definition_id),
                timeout=REGISTRY_CREATION_TIMEOUT,
            )

app/services/definitions/credential_definition_publisher.py:65: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.12/asyncio/tasks.py:519: in wait_for
    async with timeouts.timeout(timeout):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Timeout [expired]>
exc_type = <class 'asyncio.exceptions.CancelledError'>
exc_val = CancelledError(), exc_tb = <traceback object at 0x7f823e382fc0>

    async def __aexit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> Optional[bool]:
        assert self._state in (_State.ENTERED, _State.EXPIRING)
    
        if self._timeout_handler is not None:
            self._timeout_handler.cancel()
            self._timeout_handler = None
    
        if self._state is _State.EXPIRING:
            self._state = _State.EXPIRED
    
            if self._task.uncancel() <= self._cancelling and exc_type is exceptions.CancelledError:
                # Since there are no new cancel requests, we're
                # handling this.
>               raise TimeoutError from exc_val
E               TimeoutError

/usr/local/lib/python3.12/asyncio/timeouts.py:115: TimeoutError

The above exception was the direct cause of the following exception:

anyio_backend = 'asyncio'
request = <SubRequest 'credential_definition_id_revocable' for <Function test_proof_revoked_credential[clean-clean-clean-clean-clean-clean-auto_publish_true]>>
args = ()
kwargs = {'faber_client': <shared.util.rich_async_client.RichAsyncClient object at 0x7f823e374b30>, 'request': <SubRequest 'cre...JJm:2:test_schema_alt:64.43.29', name='test_schema_alt', version='64.43.29', attribute_names=['speed', 'age', 'name'])}
local_func = <function credential_definition_id_revocable at 0x7f8245887560>
backend_name = 'asyncio', backend_options = {}
runner = <anyio._backends._asyncio.TestRunner object at 0x7f823eb731a0>

    def wrapper(
        *args: Any, anyio_backend: Any, request: SubRequest, **kwargs: Any
    ) -> Any:
        # Rebind any fixture methods to the request instance
        if (
            request.instance
            and ismethod(func)
            and type(func.__self__) is type(request.instance)
        ):
            local_func = func.__func__.__get__(request.instance)
        else:
            local_func = func
    
        backend_name, backend_options = extract_backend_and_options(anyio_backend)
        if has_backend_arg:
            kwargs["anyio_backend"] = anyio_backend
    
        if has_request_arg:
            kwargs["request"] = request
    
        with get_runner(backend_name, backend_options) as runner:
            if isasyncgenfunction(local_func):
                yield from runner.run_asyncgen_fixture(local_func, kwargs)
            else:
>               yield runner.run_fixture(local_func, kwargs)

/usr/local/lib/python3.12/site-packages/anyio/pytest_plugin.py:100: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2300: in run_fixture
    retval = self.get_loop().run_until_complete(
/usr/local/lib/python3.12/asyncio/base_events.py:686: in run_until_complete
    return future.result()
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2270: in _call_in_runner_task
    return await future
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2237: in _run_tests_and_fixtures
    retval = await coro
app/tests/fixtures/definitions.py:177: in credential_definition_id_revocable
    result = await get_clean_or_regression_test_cred_def(
app/tests/fixtures/definitions.py:139: in get_clean_or_regression_test_cred_def
    result = await create_credential_definition(
app/routes/definitions.py:255: in create_credential_definition
    credential_definition_id = await cred_def_service.create_credential_definition(
app/services/definitions/credential_definitions.py:65: in create_credential_definition
    await publisher.wait_for_revocation_registry(credential_definition_id)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <app.services.definitions.credential_definition_publisher.CredentialDefinitionPublisher object at 0x7f823d7fb740>
credential_definition_id = 'JE6gMeAthoEgJyzv71a7yq:3:CL:3522:tag'

    async def wait_for_revocation_registry(self, credential_definition_id):
        try:
            self._logger.debug("Waiting for revocation registry creation")
            await asyncio.wait_for(
                wait_for_active_registry(self._controller, credential_definition_id),
                timeout=REGISTRY_CREATION_TIMEOUT,
            )
        except asyncio.TimeoutError as e:
            self._logger.error("Timeout waiting for revocation registry creation.")
>           raise CloudApiException(
                "Timeout waiting for revocation registry creation.",
                504,
            ) from e
E           app.exceptions.cloudapi_exception.CloudApiException: 504: Timeout waiting for revocation registry creation.

app/services/definitions/credential_definition_publisher.py:71: CloudApiException
import time
from typing import List

Expand Down Expand Up @@ -34,7 +35,7 @@ async def test_proof_revoked_credential(
alice_member_client: RichAsyncClient,
acme_and_alice_connection: AcmeAliceConnect,
):
time.sleep(10) # moment for revocation registry to update
await asyncio.sleep(14) # moment for revocation registry to update
# todo: remove sleep when issue resolved: https://github.com/openwallet-foundation/acapy/issues/3018

# Do proof request
Expand Down Expand Up @@ -124,7 +125,7 @@ async def test_regression_proof_revoked_credential(
alice_member_client: RichAsyncClient,
acme_and_alice_connection: AcmeAliceConnect,
):
time.sleep(10) # moment for revocation registry to update
await asyncio.sleep(14) # moment for revocation registry to update
# todo: remove sleep when issue resolved: https://github.com/openwallet-foundation/acapy/issues/3018

referent = get_or_issue_regression_cred_revoked.referent
Expand Down

0 comments on commit 65b97a1

Please sign in to comment.