Skip to content

Commit 13b1fea

Browse files
authored
docs: Explain how to correctly terminate PPE Actors (#816)
- Same as apify/apify-sdk-js#568 - See https://apify.slack.com/archives/CGZSN9DQC/p1771863296877979
1 parent 8e0e88d commit 13b1fea

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

docs/02_concepts/11_pay_per_event.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ description: Monetize your Actors using the pay-per-event pricing model
66

77
import ActorChargeSource from '!!raw-loader!roa-loader!./code/11_actor_charge.py';
88
import ConditionalActorChargeSource from '!!raw-loader!roa-loader!./code/11_conditional_actor_charge.py';
9+
import ChargeLimitCheckSource from '!!raw-loader!roa-loader!./code/11_charge_limit_check.py';
910
import ApiLink from '@site/src/components/ApiLink';
1011
import RunnableCodeBlock from '@site/src/components/RunnableCodeBlock';
1112

@@ -31,6 +32,22 @@ Then you just push your code to Apify and that's it! The SDK will even keep trac
3132

3233
If you need finer control over charging, you can access call <ApiLink to="class/Actor#get_charging_manager">`Actor.get_charging_manager()`</ApiLink> to access the <ApiLink to="class/ChargingManager">`ChargingManager`</ApiLink>, which can provide more detailed information - for example how many events of each type can be charged before reaching the configured limit.
3334

35+
### Handling the charge limit
36+
37+
While the SDK automatically prevents overcharging by limiting how many events are charged and how many items are pushed, **it does not stop your Actor from running**. When the charge limit is reached, <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> and `Actor.push_data` will silently stop charging and pushing data, but your Actor will keep running — potentially doing expensive work (scraping pages, calling APIs) for no purpose. This means your Actor may never terminate on its own if you don't check the charge limit yourself.
38+
39+
To avoid this, you should check the `event_charge_limit_reached` field in the result returned by <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> or `Actor.push_data` and stop your Actor when the limit is reached. You can also use the `chargeable_within_limit` field from the result to plan ahead — it tells you how many events of each type can still be charged within the remaining budget.
40+
41+
<RunnableCodeBlock className="language-python" language="python">
42+
{ChargeLimitCheckSource}
43+
</RunnableCodeBlock>
44+
45+
Alternatively, you can periodically check the remaining budget via <ApiLink to="class/Actor#get_charging_manager">`Actor.get_charging_manager()`</ApiLink> instead of inspecting every `ChargeResult`. This can be useful when charging happens in multiple places across your code, or when using a crawler where you don't directly control the main loop.
46+
47+
:::caution
48+
Always check the charge limit in your Actor, whether through `ChargeResult` return values or the `ChargingManager`. Without this check, your Actor will continue running and consuming platform resources after the budget is exhausted, producing no output.
49+
:::
50+
3451
## Transitioning from a different pricing model
3552

3653
When you plan to start using the pay-per-event pricing model for an Actor that is already monetized with a different pricing model, your source code will need support both pricing models during the transition period enforced by the Apify platform. Arguably the most frequent case is the transition from the pay-per-result model which utilizes the `ACTOR_MAX_PAID_DATASET_ITEMS` environment variable to prevent returning unpaid dataset items. The following is an example how to handle such scenarios. The key part is the <ApiLink to="class/ChargingManager#get_pricing_info">`ChargingManager.get_pricing_info()`</ApiLink> method which returns information about the current pricing model.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
3+
from apify import Actor
4+
5+
6+
async def main() -> None:
7+
async with Actor:
8+
urls = [
9+
'https://example.com/1',
10+
'https://example.com/2',
11+
'https://example.com/3',
12+
]
13+
14+
for url in urls:
15+
# Do some expensive work (e.g. scraping, API calls)
16+
result = {'url': url, 'data': f'Scraped data from {url}'}
17+
18+
# highlight-start
19+
# push_data returns a ChargeResult - check it to know if the budget ran out
20+
charge_result = await Actor.push_data(result, 'result-item')
21+
22+
if charge_result.event_charge_limit_reached:
23+
Actor.log.info('Charge limit reached, stopping the Actor')
24+
break
25+
# highlight-end
26+
27+
28+
if __name__ == '__main__':
29+
asyncio.run(main())

0 commit comments

Comments
 (0)