Skip to content

Commit 53b07f8

Browse files
committed
handle ServiceConflictError when reusing Actor across sequential context
1 parent 678b4db commit 53b07f8

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

src/apify/_actor.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -350,18 +350,27 @@ def event_manager(self) -> EventManager:
350350
351351
It uses `ApifyEventManager` on the Apify platform and `LocalEventManager` otherwise.
352352
"""
353-
event_manager = (
354-
ApifyEventManager(
355-
configuration=self.configuration,
356-
persist_state_interval=self.configuration.persist_state_interval,
353+
try:
354+
event_manager = (
355+
ApifyEventManager(
356+
configuration=self.configuration,
357+
persist_state_interval=self.configuration.persist_state_interval,
358+
)
359+
if self.is_at_home()
360+
else LocalEventManager(
361+
system_info_interval=self.configuration.system_info_interval,
362+
persist_state_interval=self.configuration.persist_state_interval,
363+
)
357364
)
358-
if self.is_at_home()
359-
else LocalEventManager(
360-
system_info_interval=self.configuration.system_info_interval,
361-
persist_state_interval=self.configuration.persist_state_interval,
365+
service_locator.set_event_manager(event_manager)
366+
except ServiceConflictError:
367+
self.log.debug(
368+
'Event manager in service locator was set explicitly before Actor.init was called. '
369+
'Using the existing event manager as implicit event manager for the Actor.'
362370
)
363-
)
364-
service_locator.set_event_manager(event_manager)
371+
# Use the event manager from the service locator
372+
event_manager = service_locator.get_event_manager()
373+
365374
return event_manager
366375

367376
@cached_property

tests/unit/actor/test_actor_lifecycle.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import contextlib
55
import logging
66
from typing import TYPE_CHECKING
7+
from unittest.mock import AsyncMock
78

89
import pytest
910

@@ -232,3 +233,28 @@ async def test_actor_fail_prevents_further_execution(caplog: pytest.LogCaptureFi
232233
status_records = [r for r in caplog.records if r.msg == '[Terminal status message]: cde']
233234
assert len(status_records) == 1
234235
assert status_records[0].levelno == logging.INFO
236+
237+
238+
@pytest.mark.parametrize(
239+
('first_with_call', 'second_with_call'),
240+
[
241+
pytest.param(False, False, id='both_without_call'),
242+
pytest.param(False, True, id='first_without_call'),
243+
pytest.param(True, False, id='second_without_call'),
244+
pytest.param(True, True, id='both_with_call'),
245+
],
246+
)
247+
async def test_actor_sequential_contexts(*, first_with_call: bool, second_with_call: bool) -> None:
248+
"""Test that Actor and Actor() can be used in two sequential async context manager blocks."""
249+
mock = AsyncMock()
250+
async with Actor(exit_process=False) if first_with_call else Actor as actor:
251+
await mock()
252+
assert actor._is_initialized is True
253+
254+
# After exiting the context, new Actor instance can be created without conflicts.
255+
async with Actor() if second_with_call else Actor as actor:
256+
await mock()
257+
assert actor._is_initialized is True
258+
259+
# The mock should have been called twice, once in each context.
260+
assert mock.call_count == 2

0 commit comments

Comments
 (0)