Skip to content

fix: deep-copy global WSV defaults in Workspace constructor - #1192

Merged
olemke merged 2 commits into
atmtools:mainfrom
olemke:fix-wsv-defaults
Oct 15, 2026
Merged

olemke merged 2 commits into
atmtools:mainfrom
olemke:fix-wsv-defaults

Conversation

@olemke

@olemke olemke commented Oct 15, 2026

Copy link
Copy Markdown
Member

Summary

Constructing a Workspace used to initialize its variables with a plain copy of the global defaults map. Since Wsv stores its value through a std::shared_ptr, that copy shared the underlying value objects between every workspace and the global defaults, so a method writing its out variable in place silently corrupted the same variable in all other workspaces — including workspaces created later. This branch deep-copies each default variable in the Workspace constructor and adds a regression test that pins the behavior in place.

Changes

  • src/workspace_class.h: The Workspace constructor now initializes its variable map by deep-copying each global default with Wsv::copied() (per-type copy dispatch) instead of copying the map itself, so every workspace owns independent value objects and writes to out variables stay local to that workspace.
  • tests/python/workspaces-do-not-share-defaults.py: New regression test that calls jac_targetsAddTemperature() on one workspace and asserts that a second workspace, and one constructed only after the call, are unaffected. It fails on the base commit and passes with the fix.

Reproduction

Standalone script demonstrating the bug:

"""Minimal repro: Workspace instances sharing variable payloads."""

import pyarts3 as pa

a = pa.Workspace()
b = pa.Workspace()

before = b.jac_targets.target_count()
a.jac_targetsAddTemperature()  # writes the `out` variable `jac_targets` of `a`
after = b.jac_targets.target_count()

print(f"b.jac_targets.target_count(): {before} -> {after}")
if after != before:
    raise SystemExit("BUG: calling a method on `a` corrupted `b`")
print("OK: the two workspaces are independent")

Output on the base commit (bug):

$ python3 wsv_sharing_repro.py
b.jac_targets.target_count(): 0 -> 1
BUG: calling a method on `a` corrupted `b`
exit code: 1

Expected output with this branch (fix):

$ python3 wsv_sharing_repro.py
b.jac_targets.target_count(): 0 -> 0
OK: the two workspaces are independent
exit code: 0

The corruption also affected workspaces created after the method call, because the global defaults themselves were mutated; tests/python/workspaces-do-not-share-defaults.py covers that case as well.

m-brath and others added 2 commits October 15, 2026 14:20
A plain copy of the defaults map shared the underlying value objects between
all workspaces and the global defaults, so writes to `out` variables silently
corrupted every other workspace. Use `Wsv::copied()` per variable instead.
Two workspaces must not share the value objects of variables
initialized from the global defaults: a method writing its `out`
variable in one workspace must not change the other workspace or
workspaces created later.  This guards the deep-copy introduced in
30f04ec ("deep-copy global WSV defaults in Workspace constructor").
@olemke
olemke requested a review from riclarsson October 15, 2026 12:51
@riclarsson

Copy link
Copy Markdown
Contributor

Please merge. Good find.

@olemke
olemke merged commit e58149d into atmtools:main Oct 15, 2026
9 checks passed
@olemke
olemke deleted the fix-wsv-defaults branch October 15, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants