Behavior that intentionally differs from the JS SDK (apify on npm). Everything else should match.
These differences exist to make runs cheaper or faster.
- The API clients share one HTTP client. Every API client of the SDK (the Actor's, the
storages', those of
Actor::new_client) sends its requests through the same connection pool. - API request bodies are compressed at brotli quality 1. Every API client of the SDK compresses
its request bodies of at least 1 KiB at quality 1, where the JS client uses 6: about 5x faster on
dataset items, for bodies about 14% larger.
Configuration::api_brotli_qualitysets another quality (0 to 11).
- A single-consumer request queue lists its requests only until it knows all of them. The
platform bills one read per listed request. The JS SDK lists the queue head (at most 1,000
requests, the ones in progress included) whenever its local head is nearly empty, twice per
isFinished, which the crawler calls after every request and every 500 ms. So a run with 1,000 requests in progress paid up to 2,000 reads per check, and once 1,000 were in progress the listing showed none of the others: a resurrected run could not run more than about 1,000 requests at once. Here the queue knows every unhandled request from its first add to a queue without unhandled requests (the prefetch that deduplicates added requests shows that). Otherwise (a resurrected run, a queue filled before the run), it lists all pending requests once with the request listing'spendingfilter, in the queue's order and with their data (so no request is read again): 500 per page, the next page in the background while the current one is processed. It never lists the head, andis_finishedmakes no API call: it is true once nothing it knows of is left in its local head or in progress. The queue is the run's own: requests another client adds after that are not fetched (the JS SDK picks them up from the head). - Buffered pushes (extra).
Actor::push_data_buffered,push_data_and_charge_bufferedandpush_data_buffered_withstore the items pushed within 0.5 s of each other (InitOptions::push_buffer_window) with one request, and charge them with one charge call per event; the JS SDK stores and charges every push on its own. The first 20 pushes of the run (InitOptions::unbuffered_first_pushes) are stored at once, so that the first results, and all of a short run's, come without the delay. The budget is reserved at once, and the returnedBufferedPushcan be awaited for what was stored. Buffered items are stored on everyPersistStateevent and at the exit.
initmakes its API calls at the same time. The JS SDK connects to the platform events websocket, then loads the tracked child runs, then reads the run for its pricing, one after another. Here the three are made at once, and an error of the websocket is still reported first.- The default storages and some records are read ahead during
init. On the platform,initstarts reading the metadata of the default dataset and key-value store (which opening them reads), the input record, the tracked child runs and the records ofInitOptions::prefetch_recordsin the background, and the first read of each takes what was read. What is written through the SDK before it is read (a record, items of the dataset) is read again; what another client changes in the meantime is not. The JS SDK reads each when asked. - Status messages can be set in the background.
Actor::set_status_message_in_backgroundisActor.setStatusMessage(...)withoutawait: the message is logged at once and the run updated later. Updates are sent one at a time in the order they were made, and an update is dropped when a later one was sent already (in JS, concurrent updates can land in any order);exitwaits for them before the terminal status message.
- "At home" has one source. The JS SDK reads
APIFY_IS_AT_HOMEboth throughConfiguration(coerced like a boolean, so0andfalseare false) and directly with!!process.env[...](so any non-empty value is true). Here it is alwaysConfiguration::is_at_home. - Invalid numbers are ignored. A value such as
APIFY_PROXY_PORT=abcfalls back to the default with a warning. The JS SDK throws when the configuration is constructed.
- Listed keys have an empty content type. The API does not report content types when it lists the keys of a key-value store. The JS SDK leaves the field undefined; crawlee-rs requires a string, so it is empty.
-
The final status message is awaited. The JS SDK gives the request that sets the terminal status message 1 ms before exiting. Here it gets up to 1 s (the status message timeout), since a process exit in Rust cuts off requests in flight.
-
failcannot exit with code 0.ExitOptions::exit_codehas no "not set" value, sofailtakes 0 (the default) as not set and exits with 1; the JS SDK'sfail({ exitCode: 0 })exits with 0. -
The Actor's functions panic before
Actor::init(). Calling them first is a bug in the code; the JS SDK warns and continues with an uninitialized Actor.Actor::onandActor::offare the exception: as in JS, listeners added beforeinitare queued and attached by it. -
get_envfalls back to theAPIFY_*variables. The JS SDK reads both generations of variables into one object, and a missingACTOR_*variable overwrites itsAPIFY_*predecessor withnull. Here theACTOR_*variable is read first and theAPIFY_*one is the fallback. -
use_stateuses the Actor's services. The JS SDK opens the store with the global configuration, whatever the Actor was configured with. -
Pushes that handle the rejections of the dataset's schema (extra). When a dataset's schema rejects items, the platform stores none of the request's items, and
pushDatafails (aspush_datadoes).Actor::push_data_withand the buffered pushes handle the rejected items as the push asks (OnInvalidItems): given back inPushResult::invalid_items(with their index in the push and the platform's validation errors), repaired with the rules ofpushDataWithSchemaRepairof@apify/actor-utils(logged; the items that cannot be repaired are given back), or dropped (logged). The other items are stored and charged; rejected items are not charged. -
Shared request queues can prolong locks.
extend_request_processing_timeprolongs the platform lock of a request in progress; the JS SDK never prolongs locks. -
A redirected log chunk without a newline waits for the rest of its line. The JS
StreamedLoglogs such a chunk without its last character, which looks like a bug; here the line is logged once it is complete (or when the stream ends). -
CallOptions::startstill hasmax_items. JS v4 removedmaxItemsfrom the run options. Here the field belongs to the API client'sActorStartOptions, so it stays, and the docs point tomax_total_charge_usd.
- Encrypted PKCS#8 keys are not supported for input secrets. The platform's key is a legacy
encrypted PKCS#1 PEM (DES-EDE3-CBC), which is supported, as are AES-CBC encrypted and
unencrypted keys. Node.js also reads
BEGIN ENCRYPTED PRIVATE KEYPEMs. - Pseudo-URLs escape characters above U+00FF correctly.
purlToRegExpwrites them as\xHHHH, which a JavaScript regex reads as\xHHfollowed by two literal characters, so such pseudo-URLs never match. Here they match the character. - Pseudo-URL
[regex]sections use the syntax of theregexcrate. Lookarounds and backreferences of JavaScript regexes are not supported.