Skip to content

Commit bcd87b0

Browse files
committed
refactor(core)!: drop EventEmitter dependency from SessionPool
SessionPool no longer extends EventEmitter and no longer fires a sessionRetired event. The Session->SessionPool back-reference, the sessionPool constructor option on Session, and the EVENT_SESSION_RETIRED constant are gone with it. The only consumer of that event was the browser crawler, which now retires browsers via the per-request context pipeline cleanup. Custom createSessionFunction implementations that manually constructed Session instances should drop the sessionPool argument.
1 parent 81dfe06 commit bcd87b0

9 files changed

Lines changed: 45 additions & 68 deletions

File tree

docs/upgrading/upgrading_v4.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,37 @@ new SessionPool({
127127
```typescript
128128
new SessionPool({
129129
sessionOptions: { maxUsageCount: 5 },
130-
createSessionFunction: async (pool, opts) =>
130+
createSessionFunction: async (_pool, opts) =>
131131
new Session({
132132
...opts?.sessionOptions, // already merged with pool-wide defaults
133-
sessionPool: pool,
134133
}),
135134
});
136135
```
137136

138137
If you were already spreading `pool.sessionOptions`, the change is harmless - pool defaults now appear twice in the spread chain, with the later (per-call) one winning, exactly as before.
139138

139+
## `Session` no longer requires a `sessionPool` reference
140+
141+
`Session` no longer holds a back-reference to its `SessionPool` and no longer emits a `sessionRetired` event when retired. The `sessionPool` constructor option is gone, `SessionPool` is no longer an `EventEmitter`, and the `EVENT_SESSION_RETIRED` constant is no longer exported. Custom `createSessionFunction` implementations that constructed `Session` instances manually should drop the `sessionPool` argument.
142+
143+
**Before:**
144+
```typescript
145+
new SessionPool({
146+
createSessionFunction: async (pool, opts) =>
147+
new Session({ ...opts?.sessionOptions, sessionPool: pool }),
148+
});
149+
```
150+
151+
**After:**
152+
```typescript
153+
new SessionPool({
154+
createSessionFunction: async (_pool, opts) =>
155+
new Session({ ...opts?.sessionOptions }),
156+
});
157+
```
158+
159+
If you previously subscribed to `sessionRetired` on the pool to clean up resources tied to a session, perform the cleanup at the end of your request handler (or via a context-pipeline cleanup hook) by checking `session.isUsable()` instead. `Session.retire()` is now a terminal state — once retired, `isUsable()` returns `false` permanently and cannot be undone by a subsequent `markGood()`.
160+
140161
## `retireOnBlockedStatusCodes` is removed from `Session`
141162

142163
`Session.retireOnBlockedStatusCodes` is removed. Blocked status code handling is now internal to the crawler. Configure blocked status codes via the `blockedStatusCodes` crawler option (moved from `sessionPoolOptions`).

packages/basic-crawler/src/internals/basic-crawler.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,15 +869,13 @@ export class BasicCrawler<
869869
this.sessionPool =
870870
sessionPool ??
871871
new SessionPool({
872-
createSessionFunction: async (pool, opts) =>
872+
createSessionFunction: async (_pool, opts) =>
873873
new Session({
874874
...opts?.sessionOptions,
875875
proxyInfo:
876876
opts?.sessionOptions?.proxyInfo ?? (await this.proxyConfiguration?.newProxyInfo()),
877-
sessionPool: pool,
878877
}),
879878
});
880-
this.sessionPool.setMaxListeners(20);
881879

882880
this.ownsSessionPool = !sessionPool;
883881

packages/core/src/session_pool/events.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export * from './errors.js';
2-
export * from './events.js';
32
export * from './session.js';
43
export * from './session_pool.js';
54
export * from './consts.js';

packages/core/src/session_pool/session.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { EventEmitter } from 'node:events';
2-
31
import type { Cookie as CookieObject, Dictionary, ISession, ProxyInfo, SessionState } from '@crawlee/types';
42
import ow from 'ow';
53
import type { Cookie } from 'tough-cookie';
@@ -15,7 +13,6 @@ import {
1513
} from '../cookie_utils.js';
1614
import type { CrawleeLogger } from '../log.js';
1715
import { serviceLocator } from '../service_locator.js';
18-
import { EVENT_SESSION_RETIRED } from './events.js';
1916

2017
export interface SessionOptions {
2118
/** Id of session used for generating fingerprints. It is used as proxy session name. */
@@ -66,9 +63,6 @@ export interface SessionOptions {
6663
*/
6764
maxUsageCount?: number;
6865

69-
/** SessionPool instance. Session will emit the `sessionRetired` event on this instance. */
70-
sessionPool?: import('./session_pool.js').SessionPool;
71-
7266
log?: CrawleeLogger;
7367
errorScore?: number;
7468
cookieJar?: CookieJar;
@@ -91,7 +85,6 @@ export class Session implements ISession {
9185
private _expiresAt: Date;
9286
private _usageCount: number;
9387
private _maxUsageCount: number;
94-
private sessionPool: import('./session_pool.js').SessionPool;
9588
private _errorScore: number;
9689
private _retired = false;
9790
private _proxyInfo?: ProxyInfo;
@@ -137,11 +130,10 @@ export class Session implements ISession {
137130
/**
138131
* Session configuration.
139132
*/
140-
constructor(options: SessionOptions) {
133+
constructor(options: SessionOptions = {}) {
141134
ow(
142135
options,
143136
ow.object.exactShape({
144-
sessionPool: ow.object.instanceOf(EventEmitter),
145137
id: ow.optional.string,
146138
cookieJar: ow.optional.object,
147139
proxyInfo: ow.optional.object,
@@ -159,7 +151,6 @@ export class Session implements ISession {
159151
);
160152

161153
const {
162-
sessionPool,
163154
id = `session_${cryptoRandomObjectId(10)}`,
164155
cookieJar = new CookieJar(),
165156
proxyInfo = undefined,
@@ -192,7 +183,6 @@ export class Session implements ISession {
192183
this._usageCount = usageCount; // indicates how many times the session has been used
193184
this._errorScore = errorScore; // indicates number of markBaded request with the session
194185
this._maxUsageCount = maxUsageCount;
195-
this.sessionPool = sessionPool;
196186
}
197187

198188
/**
@@ -273,9 +263,6 @@ export class Session implements ISession {
273263
this._errorScore += this._maxErrorScore;
274264
this._usageCount += 1;
275265
this._retired = true;
276-
277-
// emit event so we can retire browser in puppeteer pool
278-
this.sessionPool.emit(EVENT_SESSION_RETIRED, this);
279266
}
280267

281268
/**

packages/core/src/session_pool/session_pool.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { EventEmitter } from 'node:events';
2-
31
import type { Dictionary } from '@crawlee/types';
42
import { AsyncQueue } from '@sapphire/async-queue';
53
import ow from 'ow';
@@ -132,7 +130,7 @@ export interface SessionPoolOptions {
132130
*
133131
* @category Scaling
134132
*/
135-
export class SessionPool extends EventEmitter {
133+
export class SessionPool {
136134
private static nextId = 0;
137135

138136
readonly id: string;
@@ -155,8 +153,6 @@ export class SessionPool extends EventEmitter {
155153
private roundRobinIndex = 0;
156154

157155
constructor(options: SessionPoolOptions = {}) {
158-
super();
159-
160156
ow(
161157
options,
162158
ow.object.exactShape({
@@ -453,16 +449,13 @@ export class SessionPool extends EventEmitter {
453449
* @returns New session.
454450
*/
455451
protected async _defaultCreateSessionFunction(
456-
sessionPool: SessionPool,
452+
_sessionPool: SessionPool,
457453
options: { sessionOptions?: SessionOptions } = {},
458454
): Promise<Session> {
459455
ow(options, ow.object.exactShape({ sessionOptions: ow.optional.object }));
460456
const { sessionOptions = {} } = options;
461457

462-
return new Session({
463-
...sessionOptions,
464-
sessionPool,
465-
});
458+
return new Session(sessionOptions);
466459
}
467460

468461
/**
@@ -532,7 +525,6 @@ export class SessionPool extends EventEmitter {
532525
});
533526

534527
for (const sessionObject of loadedSessionPool.sessions) {
535-
sessionObject.sessionPool = this;
536528
sessionObject.createdAt = new Date(sessionObject.createdAt as string);
537529
sessionObject.expiresAt = new Date(sessionObject.expiresAt as string);
538530
const recreatedSession = await this._invokeCreateSessionFunction(sessionObject);

test/core/crawlers/puppeteer_crawler.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ describe('PuppeteerCrawler', () => {
331331

332332
saveResponseCookies: true,
333333
sessionPool: new SessionPool({
334-
createSessionFunction: (sessionPool) => {
335-
const session = new Session({ sessionPool });
334+
createSessionFunction: () => {
335+
const session = new Session();
336336
session.setCookies(cookies, serverUrl);
337337
return session;
338338
},

test/core/session_pool/session.test.ts

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { EVENT_SESSION_RETIRED, log, Session, SessionPool } from '@crawlee/core';
1+
import { log, Session } from '@crawlee/core';
22
import { ResponseWithUrl } from '@crawlee/http-client';
33
import { entries, sleep } from '@crawlee/utils';
44
import { CookieJar } from 'tough-cookie';
55

66
describe('Session - testing session behaviour', () => {
7-
let sessionPool: SessionPool;
87
let session: Session;
98

109
beforeEach(() => {
11-
sessionPool = new SessionPool();
12-
session = new Session({ sessionPool });
10+
session = new Session();
1311
});
1412

1513
test('should markGood session and lower the errorScore', () => {
@@ -24,20 +22,14 @@ describe('Session - testing session behaviour', () => {
2422
expect(session.errorScore).toBe(0.5);
2523
});
2624

27-
test('should throw error when param sessionPool is not EventEmitter instance', () => {
28-
const err = 'Expected property object `sessionPool` `{}` to be of type `EventEmitter` in object';
29-
// @ts-expect-error JS-side validation
30-
expect(() => new Session({ sessionPool: {} })).toThrow(err);
31-
});
32-
3325
test('should mark session markBad', () => {
3426
session.markBad();
3527
expect(session.errorScore).toBe(1);
3628
expect(session.usageCount).toBe(1);
3729
});
3830

3931
test('should expire session', async () => {
40-
session = new Session({ maxAgeSecs: 1 / 100, sessionPool });
32+
session = new Session({ maxAgeSecs: 1 / 100 });
4133
await sleep(101);
4234
expect(session.isExpired()).toBe(true);
4335
expect(session.isUsable()).toBe(false);
@@ -81,13 +73,7 @@ describe('Session - testing session behaviour', () => {
8173
});
8274

8375
test('should retire session', () => {
84-
let discarded = false;
85-
sessionPool.on(EVENT_SESSION_RETIRED, (ses) => {
86-
expect(ses instanceof Session).toBe(true);
87-
discarded = true;
88-
});
8976
session.retire();
90-
expect(discarded).toBe(true);
9177
expect(session.usageCount).toBe(1);
9278
expect(session.isUsable()).toBe(false);
9379
});
@@ -160,7 +146,7 @@ describe('Session - testing session behaviour', () => {
160146
});
161147

162148
test('should use cookieJar', () => {
163-
session = new Session({ sessionPool });
149+
session = new Session();
164150
expect(session.cookieJar.setCookie).toBeDefined();
165151
});
166152

@@ -171,7 +157,7 @@ describe('Session - testing session behaviour', () => {
171157
{ name: 'cookie2', value: 'your-cookie' },
172158
];
173159

174-
session = new Session({ sessionPool });
160+
session = new Session();
175161
session.setCookies(cookies, url);
176162
expect(session.getCookieString(url)).toBe('cookie1=my-cookie; cookie2=your-cookie');
177163
});
@@ -180,7 +166,7 @@ describe('Session - testing session behaviour', () => {
180166
const url = 'https://example.com';
181167
const cookies = [{ name: 'session_cookie', value: 'session-cookie-value', expires: -1 }];
182168

183-
session = new Session({ sessionPool });
169+
session = new Session();
184170
session.setCookies(cookies, url);
185171
expect(session.getCookieString(url)).toBe('session_cookie=session-cookie-value');
186172
});
@@ -192,7 +178,7 @@ describe('Session - testing session behaviour', () => {
192178
{ name: 'cookie2', value: 'your-cookie', domain: '.example.com' },
193179
];
194180

195-
session = new Session({ sessionPool });
181+
session = new Session();
196182
session.setCookies(cookies, url);
197183
expect(session.getCookieString(url)).toBe('cookie2=your-cookie');
198184
});
@@ -206,14 +192,14 @@ describe('Session - testing session behaviour', () => {
206192
spy: true,
207193
});
208194

209-
session = new Session({ sessionPool, log: mockedLog } as any);
195+
session = new Session({ log: mockedLog } as any);
210196
session.setCookies(cookies, url);
211197
expect(session.getCookieString(url)).toBe('');
212198
expect(mockedLog.warning).toHaveBeenCalledOnce();
213199
});
214200

215201
test('setCookie does not throw on malformed raw cookie string', () => {
216-
session = new Session({ sessionPool });
202+
session = new Session();
217203
expect(() => session.setCookie('garbled!!!@#$%nonsense', 'https://www.example.com')).not.toThrow();
218204
});
219205

@@ -224,7 +210,7 @@ describe('Session - testing session behaviour', () => {
224210
{ name: 'cookie2', value: 'your-cookie', domain: 'example.com' },
225211
];
226212

227-
session = new Session({ sessionPool });
213+
session = new Session();
228214
session.setCookies(cookies, url);
229215
expect(session.getCookieString(url)).toBe('');
230216
expect(session.getCookieString('https://example.com')).toBe('cookie2=your-cookie');
@@ -234,7 +220,6 @@ describe('Session - testing session behaviour', () => {
234220
const url = 'https://www.example.com';
235221

236222
session = new Session({
237-
sessionPool,
238223
cookieJar: CookieJar.fromJSON(
239224
JSON.stringify({
240225
cookies: [
@@ -264,7 +249,6 @@ describe('Session - testing session behaviour', () => {
264249
const url = 'https://www.example.com';
265250

266251
session = new Session({
267-
sessionPool,
268252
cookieJar: CookieJar.fromJSON(
269253
JSON.stringify({
270254
cookies: [
@@ -297,7 +281,7 @@ describe('Session - testing session behaviour', () => {
297281
headers.append('set-cookie', 'CSRF=e8b667; Domain=example.com; Secure ');
298282
headers.append('set-cookie', 'id=a3fWa; Expires=Wed, Domain=example.com; 21 Oct 2015 07:28:00 GMT');
299283

300-
const newSession = new Session({ sessionPool: new SessionPool() });
284+
const newSession = new Session();
301285
const url = 'https://example.com';
302286
newSession.setCookiesFromResponse(new ResponseWithUrl('', { headers, url }));
303287
let cookies = newSession.getCookieString(url);
@@ -317,7 +301,7 @@ describe('Session - testing session behaviour', () => {
317301
headers.append('set-cookie', 'CSRF=e8b667; Domain=example.com; Secure ');
318302
headers.append('set-cookie', 'id=a3fWa; Expires=Wed, Domain=example.com; 21 Oct 2015 07:28:00 GMT');
319303

320-
const newSession = new Session({ sessionPool: new SessionPool() });
304+
const newSession = new Session();
321305
const url = 'https://example.com';
322306
newSession.setCookiesFromResponse(new ResponseWithUrl('', { headers, url }));
323307

@@ -329,7 +313,7 @@ describe('Session - testing session behaviour', () => {
329313
old.expiresAt = new Date(old.expiresAt);
330314

331315
// @ts-expect-error string -> Date for createdAt has been overridden
332-
const reinitializedSession = new Session({ sessionPool, ...old });
316+
const reinitializedSession = new Session({ ...old });
333317
expect(reinitializedSession.getCookieString(url)).toEqual('CSRF=e8b667; id=a3fWa');
334318
});
335319
});

test/core/session_pool/session_pool.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ describe('SessionPool - testing session pool', () => {
6969
expect(session.maxAgeSecs).toEqual(sessionPool.sessionOptions.maxAgeSecs);
7070
// @ts-expect-error Accessing private property
7171
expect(session.maxUsageCount).toEqual(sessionPool.sessionOptions.maxUsageCount);
72-
// @ts-expect-error Accessing private property
73-
expect(session.sessionPool).toEqual(sessionPool);
7472
});
7573

7674
test('should pick session when pool is full', async () => {
@@ -336,7 +334,7 @@ describe('SessionPool - testing session pool', () => {
336334
const createSessionFunction = (sessionPool2: SessionPool) => {
337335
isCalled = true;
338336
expect(sessionPool2 instanceof SessionPool).toBe(true);
339-
return new Session({ sessionPool: sessionPool2 });
337+
return new Session();
340338
};
341339
const newSessionPool = new SessionPool({ createSessionFunction });
342340
const session = await newSessionPool.getSession();
@@ -361,7 +359,7 @@ describe('SessionPool - testing session pool', () => {
361359
});
362360

363361
test('should be able to add session instance and create new session with provided sessionOptions with addSession()', async () => {
364-
const session = new Session({ sessionPool, id: 'test-session-instance' });
362+
const session = new Session({ id: 'test-session-instance' });
365363
await sessionPool.addSession(session);
366364

367365
await sessionPool.addSession({ id: 'test-session' });

0 commit comments

Comments
 (0)