Skip to content

fix(odin): Fix checkin worker picklable#556

Open
vikramlc-cognite wants to merge 3 commits into
masterfrom
EDG-679-fix-checkin-worker-picklable
Open

fix(odin): Fix checkin worker picklable#556
vikramlc-cognite wants to merge 3 commits into
masterfrom
EDG-679-fix-checkin-worker-picklable

Conversation

@vikramlc-cognite

Copy link
Copy Markdown
Contributor

Summary

Fixes CheckinWorker so it can be pickled, unblocking extractor startup on macOS/Windows where multiprocessing defaults to the spawn start method.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behavior)
  • Refactor (no functional change)
  • Documentation update
  • Chore / tooling / CI

What changed

  • cognite/extractorutils/unstable/core/checkin_worker.py: added CheckinWorker.getstate/setstate. getstate drops _lock and _flush_lock
    (both threading.RLock) from the pickled state; setstate recreates fresh RLock instances after unpickling.
  • cognite/extractorutils/unstable/core/runtime.py: removed a leftover local-checkout debug log line (unrelated stray change already on the branch).
  • .gitignore: resolved an unrelated, unresolved merge-conflict marker block (<<<<<<< Updated upstream / ======= / >>>>>>> Stashed changes) already
    present on the branch, collapsing it to the single intended /files entry.
  • tests/test_unstable/test_checkin_worker.py: added two tests, test_checkin_worker_pickle_round_trip and
    test_checkin_worker_is_picklable_with_spawn_start_method (see below).

Why it changed

  • Related issue: EDG-679 (https://cognitedata.atlassian.net/browse/EDG-679)
  • Runtime._spawn_extractor passes a live CheckinWorker instance as an argument to multiprocessing.Process(...). On Linux, the default start method
    is fork, which doesn't pickle args, so the bug is invisible in CI/Docker. On macOS and Windows, the default start method is spawn, which pickles
    every argument. CheckinWorker held threading.RLock instances (_lock, _flush_lock), and RLock objects can't be pickled, so the extractor crashed
    immediately on startup with TypeError: cannot pickle '_thread.RLock' object before any provider/destination code ran — a full local-dev blocker on
    macOS/Windows.

What to focus on during review

  • The getstate/setstate pair only strips the two locks; every other attribute (including the CogniteClient, Logger, queued errors/task
    updates/action updates, and active_revision) round-trips through pickling unchanged. Worth double-checking no other non-picklable attribute gets
    added to CheckinWorker in the future without updating getstate.
  • Locks are intentionally not preserved across the pickle boundary — a lock inherited from the parent process would be meaningless in the child
    anyway, so fresh locks are correct here rather than something to reconcile.
  • Typed the state dict as dict[str, object] rather than dict[str, Any] to keep ANN401 clean, per repo convention.
  • The two unrelated cleanups (runtime.py debug log line, .gitignore conflict block) were pre-existing on this branch before I started — flagging in
    case you want them split into a separate commit, though they're trivial enough I folded them in.

Test evidence

  • test_checkin_worker_pickle_round_trip: pickles/unpickles a CheckinWorker directly and asserts queued state (active_revision, _task_updates)
    survives, and that the recreated _lock/_flush_lock are new, distinct, usable RLock objects.
  • test_checkin_worker_is_picklable_with_spawn_start_method: reproduces the actual bug — spawns a real
    multiprocessing.get_context("spawn").Process(...) with a live CheckinWorker as a target arg, matching Runtime._spawn_extractor's exact usage.
  • Verified both tests fail with the exact reported TypeError: cannot pickle '_thread.RLock' object when the fix is reverted, and pass with it
    applied.
  • ruff check / ruff format --check: clean on all changed files.
  • mypy: clean on checkin_worker.py and runtime.py.
  • Full non-integration unit suite (tests/tests_unit): 133 passed, 1 pre-existing unrelated failure (test_load_config_keyvault, missing
    KEYVAULT_TENANT_ID env var — confirmed failing on master too, unrelated to this change).
  • tests/test_unstable and tests/tests_integration require live CDF dev credentials (COGNITE_DEV_* env vars) not available in this environment —
    pre-existing constraint, not something this change affects; the two new tests were deliberately written to avoid needing those credentials.

Risks and unknowns

  • None identified for the core fix — it's a narrow, additive change (drop/recreate two locks) with no behavioral change on fork-based platforms
    (Linux), which is why the bug was invisible there in the first place.
  • Separately, unrelated to this ticket: .gitignore line 144 has a stray ======= merge-conflict marker already committed on master (predates this
    branch). Harmless to git (matches no files) but worth a follow-up cleanup PR.

Rollout and rollback

  • No migrations, flags, or config changes. Pure library code change — revert is a straightforward git revert if needed.

Checklist

  • Self-reviewed the diff
  • Tests added or updated (or N/A with reason)
  • Docs updated (or N/A) — N/A, no public API/behavior change requiring doc updates
  • No secrets, credentials, or PII committed
  • Breaking changes called out above and communicated to affected teams — N/A, not a breaking change

@vikramlc-cognite vikramlc-cognite self-assigned this Jul 14, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements custom __getstate__ and __setstate__ methods for CheckinWorker to enable pickling by dropping and recreating unpicklable threading.RLock instances. This resolves issues when spawning processes on macOS and Windows. Corresponding unit tests have been added to verify the pickling round-trip and behavior under the spawn start method. Feedback suggests terminating the spawned test process in the finally block if it remains alive to prevent leaked background processes in CI.

Comment thread tests/test_unstable/test_checkin_worker.py
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.82%. Comparing base (b3b1121) to head (9fa2d6b).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #556      +/-   ##
==========================================
+ Coverage   83.76%   83.82%   +0.05%     
==========================================
  Files          46       46              
  Lines        4627     4636       +9     
==========================================
+ Hits         3876     3886      +10     
+ Misses        751      750       -1     
Files with missing lines Coverage Δ
...ite/extractorutils/unstable/core/checkin_worker.py 92.82% <100.00%> (+0.72%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vikramlc-cognite
vikramlc-cognite marked this pull request as ready for review July 15, 2026 09:41
@vikramlc-cognite
vikramlc-cognite requested a review from a team as a code owner July 15, 2026 09:41
@Yaseen-A-Khan

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request adds __getstate__ and __setstate__ methods to CheckinWorker to allow pickling when using spawn-based multiprocessing, which is the default on macOS and Windows. This is achieved by dropping the unpicklable RLock objects during pickling and recreating them upon unpickling. Additionally, unit tests are added to verify pickling round-trips and spawn-based process execution. The reviewer suggested using dict.pop(key, None) instead of del when removing locks from the state dictionary to prevent potential KeyErrors if the locks are not present.

Comment thread cognite/extractorutils/unstable/core/checkin_worker.py

@Yaseen-A-Khan Yaseen-A-Khan 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.

LGTM

@vikramlc-cognite vikramlc-cognite added the waiting-for-risk-review Waiting for a member of the risk review team to take an action label Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-for-risk-review Waiting for a member of the risk review team to take an action

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants