Thanks to visit codestin.com
Credit goes to github.com

Skip to content

FIX Don't send ScoringMonitor._log to the pickler - #34821

Merged
lesteve merged 5 commits into
scikit-learn:mainfrom
jeremiedbb:fix-scoring-monitor-pickle
Aug 27, 2026
Merged

FIX Don't send ScoringMonitor._log to the pickler#34821
lesteve merged 5 commits into
scikit-learn:mainfrom
jeremiedbb:fix-scoring-monitor-pickle

Conversation

@jeremiedbb

Copy link
Copy Markdown
Member

Fixes #34816
It should also fix the main CI.

Sending the _log, that is being mutated by threads or subprocesses, to the pickler raises a pickling error, as seen in #34816. The error was added in Python 3.14.7 but the bug was already there silently before. This is why it was caught by #34806: the doc build bumped python version to 3.14.7.

The issue and the fix is very similar to the one for RecordingCallback, see #34811, except that here we can't send an empty log since we want to be able to pickle and unpickle the callback and retrieve the log. So we send a snapshot of the log instead of the live log being mutated.

I wrote a test, heavily helped by claude, to ensure that we don't send to the pickler the live containers that the listener threads mutate. This way it should prevent us to do the same error for future callbacks. (note that progressbar is not impacted since the queue lives at the module level and is thus not sent to the pickler)

@FrancoisPgm FrancoisPgm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from a nitpick and a suggestion for a test to make codecov happy, LGTM, thanks.

Comment thread sklearn/callback/tests/test_pickle.py Outdated
Comment thread sklearn/callback/tests/test_pickle.py Outdated

def persistent_id(self, obj):
if id(obj) in self._watched:
self.walked_through.add(obj.__class__.__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codecov seems unhappy that this line is not executed, a test could be added to test _TraversalRecorder with a callback that deliberately sends an unpicklable attr. Here is a suggestion for such a test:

class UnpicklableCallback:
    """A callback that can't be pickled.

    This callbacks sends to pickle an attr that can be mutated during pickling by
    another thread."""

    def __init__(self):
        self.unpicklable_attr = []
        self._listener_handle = open_listener(self.unpicklable_attr.append, owner=self)

    def setup(self, estimator, context):
        pass

    def on_fit_task_begin(self, estimator, context):
        pass

    def on_fit_task_end(self, estimator, context):
        pass

    def teardown(self, estimator, context):
        pass


def test_TraversalRecorder(monkeypatch):
    """Test _checked's assert does fail with unpicklable callback."""
    callback = UnpicklableCallback()

    monkeypatch.setattr(
        callback.__class__,
        "on_fit_task_begin",
        _checked(callback.__class__.on_fit_task_begin),
    )

    with pytest.raises(AssertionError):
        MaxIterEstimator(max_iter=3).set_callbacks(callback).fit()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like adding a test to cover the test. The fact that the test fails on main is enough, that's what we usually consider enough. So I refactored it in a way that avoids the uncovered line.

@jeremiedbb jeremiedbb changed the title MNT Don't send ScoringMonitor._log to the pickler FIX Don't send ScoringMonitor._log to the pickler Aug 27, 2026
@jeremiedbb jeremiedbb added this to Labs Aug 27, 2026
@jeremiedbb jeremiedbb moved this to PR waiting for reviews in Labs Aug 27, 2026

@lesteve lesteve left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not going to pretend I understand everything that is going on, but the fix seems reasonable and this will make CircleCI green again on main

@lesteve
lesteve merged commit 3086a75 into scikit-learn:main Aug 27, 2026
38 checks passed
@github-project-automation github-project-automation Bot moved this from PR waiting for reviews to Done in Labs Aug 27, 2026
prady0t pushed a commit to prady0t/scikit-learn that referenced this pull request Sep 2, 2026
@jeremiedbb
jeremiedbb deleted the fix-scoring-monitor-pickle branch September 3, 2026 15:25
@jeremiedbb jeremiedbb mentioned this pull request Sep 8, 2026
14 tasks
jeremiedbb added a commit to jeremiedbb/scikit-learn that referenced this pull request Sep 8, 2026
jeremiedbb added a commit to jeremiedbb/scikit-learn that referenced this pull request Sep 8, 2026
jeremiedbb added a commit to jeremiedbb/scikit-learn that referenced this pull request Sep 8, 2026
jeremiedbb added a commit to jeremiedbb/scikit-learn that referenced this pull request Sep 9, 2026
jeremiedbb added a commit to jeremiedbb/scikit-learn that referenced this pull request Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ScoringMonitor is mutated by its listener thread while joblib pickles the estimator

3 participants