Repository navigation
fix(sessions)!: Never reuse a retired session #2348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ def __init__( | |
| usage_count: int = 0, | ||
| max_usage_count: int = 50, | ||
| error_score: float = 0.0, | ||
| retired: bool = False, | ||
| cookies: SessionCookies | CookieJar | dict[str, str] | list[CookieParam] | None = None, | ||
| blocked_status_codes: list | None = None, | ||
| ) -> None: | ||
|
|
@@ -60,6 +61,7 @@ def __init__( | |
| usage_count: Number of times the session has been used. | ||
| max_usage_count: Maximum allowable uses of the session before it is considered expired. | ||
| error_score: Current error score of the session. | ||
| retired: Whether the session has been permanently retired. | ||
| cookies: Cookies associated with the session. | ||
| blocked_status_codes: HTTP status codes that indicate a session should be blocked. | ||
| """ | ||
|
|
@@ -72,6 +74,7 @@ def __init__( | |
| self._usage_count = usage_count | ||
| self._max_usage_count = max_usage_count | ||
| self._error_score = error_score | ||
| self._retired = retired | ||
| self._cookies = SessionCookies(cookies) or SessionCookies() | ||
| self._blocked_status_codes = set(blocked_status_codes or self._DEFAULT_BLOCKED_STATUS_CODES) | ||
|
|
||
|
|
@@ -138,14 +141,19 @@ def usage_count(self) -> float: | |
| """Get the current usage count.""" | ||
| return self._usage_count | ||
|
|
||
| @property | ||
| def retired(self) -> bool: | ||
| """Indicate whether the session has been permanently retired.""" | ||
| return self._retired | ||
|
|
||
| @property | ||
| def expires_at(self) -> datetime: | ||
| """Get the expiration datetime of the session.""" | ||
| return self._created_at + self._max_age | ||
|
|
||
| @property | ||
| def is_blocked(self) -> bool: | ||
| """Indicate whether the session is blocked based on the error score..""" | ||
| """Indicate whether the session is blocked by its error score.""" | ||
| return self._error_score >= self._max_error_score | ||
|
|
||
| @property | ||
|
|
@@ -161,7 +169,7 @@ def is_max_usage_count_reached(self) -> bool: | |
| @property | ||
| def is_usable(self) -> bool: | ||
| """Determine if the session is usable for next requests.""" | ||
| return not (self.is_blocked or self.is_expired or self.is_max_usage_count_reached) | ||
| return not (self._retired or self.is_blocked or self.is_expired or self.is_max_usage_count_reached) | ||
|
|
||
| @overload | ||
| def get_state(self, *, as_dict: Literal[True]) -> dict: ... | ||
|
|
@@ -183,6 +191,7 @@ def get_state(self, *, as_dict: bool = False) -> SessionModel | dict: | |
| usage_count=self._usage_count, | ||
| max_usage_count=self._max_usage_count, | ||
| error_score=self._error_score, | ||
| retired=self._retired, | ||
| cookies=self._cookies.get_cookies_as_dicts(), | ||
| blocked_status_codes=list(self._blocked_status_codes), | ||
| ) | ||
|
|
@@ -192,14 +201,17 @@ def get_state(self, *, as_dict: bool = False) -> SessionModel | dict: | |
|
|
||
| def mark_good(self) -> None: | ||
| """Mark the session as good. Should be called after a successful session usage.""" | ||
| if self._retired: | ||
| return | ||
|
|
||
| self._usage_count += 1 | ||
|
|
||
| if self._error_score > 0: | ||
| self._error_score = max(0, self._error_score - self._error_score_decrement) | ||
|
|
||
| # Retire the session if it is not usable anymore | ||
| if not self.is_usable: | ||
| self.retire() | ||
| self._retired = True | ||
|
|
||
| def mark_bad(self) -> None: | ||
| """Mark the session as bad after an unsuccessful session usage.""" | ||
|
|
@@ -208,17 +220,19 @@ def mark_bad(self) -> None: | |
|
|
||
| # Retire the session if it is not usable anymore | ||
| if not self.is_usable: | ||
| self.retire() | ||
| self._retired = True | ||
|
|
||
| def retire(self) -> None: | ||
| """Retire the session by setting the error score to the maximum value. | ||
| """Permanently retire the session and increase its error score once. | ||
|
|
||
| This method should be used if the session usage was unsuccessful and you are sure that it is because of | ||
| the session configuration and not any external matters. For example when server returns 403 status code. | ||
| If the session does not work due to some external factors as server error such as 5XX you probably want | ||
| to use `mark_bad` method. | ||
| """ | ||
| self._error_score += self._max_error_score | ||
| if not self._retired: | ||
| self._retired = True | ||
| self._error_score += self._max_error_score | ||
|
Comment on lines
+233
to
+235
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any point in doing this for retired session?
if not, I would suggest to do just
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. The session pool only needs the retired flag through is_usable, so increasing the score is not required to keep a retired session out of the pool. I kept the explicit retire() penalty to preserve its existing error_score/is_blocked behavior. Crawlee JS also still adds maxErrorScore in explicit retire(); automatic retirement now sets only the flag in this PR, as requested in the earlier review. Making retire() flag-only would mean a manually retired session can report is_blocked=False, even though it remains permanently unusable. That is reasonable if we want to separate those concepts fully, but it is an additional observable behavior change. @vdusek, would you prefer that policy for v2? Happy to simplify retire() and update the tests and migration notes once the intended behavior is confirmed.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but that would diverge from JS, where |
||
|
|
||
| def is_blocked_status_code( | ||
| self, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.