Skip to content

Commit e87405e

Browse files
fix: size the shared crawler pool from the run, not from a request
1 parent 79a4e2f commit e87405e

5 files changed

Lines changed: 36 additions & 13 deletions

File tree

‎src/input.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ async function getProxyConfiguration(proxyOptions: ProxyOptions) {
4646

4747
const DEFAULT_PROXY_OPTIONS = ragWebBrowserInputSchema.properties.proxyConfiguration.default as ProxyOptions;
4848

49+
let runDesiredConcurrency = ragWebBrowserInputSchema.properties.desiredConcurrency.default;
50+
4951
/**
5052
* Resolves the proxy from the request Crawlee is about to send, so one crawler serves callers that
5153
* each asked for a different proxy. This is what keeps the Standby crawler set at three.
@@ -80,6 +82,7 @@ async function validateProxyConfiguration(proxyOptions: ProxyOptions, abortOnFai
8082
export async function processStandbyInput(originalInput: Partial<Input>) {
8183
const { input, searchCrawlerOptions, contentScraperSettings } = await processInputInternal(originalInput, true);
8284

85+
runDesiredConcurrency = input.desiredConcurrency;
8386
await validateProxyConfiguration(input.proxyConfiguration, true);
8487
const contentCrawlerOptions: ContentCrawlerOptions[] = [
8588
createPlaywrightCrawlerOptions(input),
@@ -97,6 +100,11 @@ export async function processInput(originalInput: Partial<Input>) {
97100

98101
// In Standby a proxy problem must fail one request; in Normal mode there is nothing to serve.
99102
await validateProxyConfiguration(input.proxyConfiguration, !isActorStandby());
103+
104+
// A Standby request builds these options whenever no crawler is cached yet, so whichever caller
105+
// got there first would size the pool every later caller shares. The run sizes it instead.
106+
if (isActorStandby()) input.desiredConcurrency = runDesiredConcurrency;
107+
100108
const contentCrawlerOptions: ContentCrawlerOptions = input.scrapingTool === 'raw-http'
101109
? createCheerioCrawlerOptions(input, false)
102110
: createPlaywrightCrawlerOptions(input, false);

‎src/main.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ Actor.on('migrating', failAllResponsesOnMigration);
1717

1818
const originalInput = await Actor.getInput<Partial<Input>>() ?? {} as Input;
1919

20-
// The log level is process-wide, so it follows the Actor's own input. In Standby `debugMode` arrives
21-
// per request, where honoring it would switch logging for every other caller sharing the run.
20+
// Process-wide, so it follows the run's input: a caller's `debugMode` must not reach other callers.
2221
log.setLevel(originalInput.debugMode ? log.LEVELS.DEBUG : log.LEVELS.INFO);
2322

2423
if (isActorStandby()) {

‎src/responses.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,8 @@ export function sendResponseIfFinished(responseId: string) {
165165
}
166166

167167
/**
168-
* Tells everyone still waiting that this run is moving to another server, so they retry instead of
169-
* waiting out their own timeout. Sent at once rather than on a timer, because the announcement is
170-
* the last moment we know the process is still here to answer with.
168+
* Tells everyone still waiting to retry elsewhere. Sent at once rather than on a timer, because the
169+
* announcement is the last moment we know the process is still here to answer with.
171170
*/
172171
export function failAllResponsesOnMigration() {
173172
for (const responseId of [...responseData.keys()]) {

‎tests/input.test.ts‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Actor } from 'apify';
12
import { log } from 'crawlee';
23
import { afterEach, describe, expect, it, vi } from 'vitest';
34

@@ -6,6 +7,12 @@ import { parseParameters } from '../src/utils.js';
67

78
process.env.ACTOR_FULL_NAME = 'apify/rag-web-browser';
89

10+
// A custom proxy keeps `Actor.createProxyConfiguration` off the network.
11+
const CALLER_PROXY = encodeURIComponent(JSON.stringify({
12+
useApifyProxy: false,
13+
proxyUrls: ['http://caller.invalid:8000'],
14+
}));
15+
916
describe('warnIfLowMemoryForPlaywright', () => {
1017
afterEach(() => {
1118
vi.restoreAllMocks();
@@ -38,15 +45,26 @@ describe('warnIfLowMemoryForPlaywright', () => {
3845

3946
describe('the log level', () => {
4047
it('ignores a caller\'s debugMode, which would switch logging for everyone sharing the run', async () => {
41-
// A custom proxy keeps `Actor.createProxyConfiguration` off the network.
42-
const proxy = encodeURIComponent(JSON.stringify({
43-
useApifyProxy: false,
44-
proxyUrls: ['http://log-level.invalid:8000'],
45-
}));
4648
log.setLevel(log.LEVELS.INFO);
4749

48-
await processInput(parseParameters(`?query=hello&debugMode=true&proxyConfiguration=${proxy}`));
50+
await processInput(parseParameters(`?query=hello&debugMode=true&proxyConfiguration=${CALLER_PROXY}`));
4951

5052
expect(log.getLevel()).toBe(log.LEVELS.INFO);
5153
});
5254
});
55+
56+
describe('the shared crawler pool', () => {
57+
afterEach(() => {
58+
vi.restoreAllMocks();
59+
});
60+
61+
it('is sized by the run, not by whichever caller happens to build it', async () => {
62+
vi.spyOn(Actor, 'getEnv').mockReturnValue({ metaOrigin: 'STANDBY' } as never);
63+
64+
const { contentCrawlerOptions } = await processInput(
65+
parseParameters(`?query=hello&desiredConcurrency=17&proxyConfiguration=${CALLER_PROXY}`),
66+
);
67+
68+
expect(contentCrawlerOptions.crawlerOptions.autoscaledPoolOptions?.desiredConcurrency).toBe(5);
69+
});
70+
});

‎tests/responses.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ describe('a run that migrates to another server', () => {
1717
await expect(waiting).rejects.toThrow('Actor had to migrate to another server. Please, retry your request.');
1818
});
1919

20-
// This is what makes it safe to answer the moment the migration is announced, instead of giving
21-
// the crawl a grace period the process may not live long enough to honour.
20+
// This is what makes it safe to answer at once instead of giving the crawl a grace period.
2221
it('still returns the results a caller already has', async () => {
2322
const waiting = createResponsePromise('one-page-finished', 300);
2423
const url = 'https://example.com/finished';

0 commit comments

Comments
 (0)