Skip to content

LocalFileIdentifiableStore (and CouchDBIdentifiableStore): in-memory mutations are not persisted to disk/database #552

@s-heppner

Description

@s-heppner

Mutating an object that was retrieved from LocalFileIdentifiableStore (or CouchDBIdentifiableStore) does not write the change back to the underlying storage. Once the object is evicted from the in-process weak-reference cache — for example when a second process or worker reads the same object — the stale on-disk/database value is returned.

This was caused by PR #370, which removed Referable.commit() and the LocalFileBackend/CouchDBBackend classes. Before the refactor, every mutation handler in the server called .commit() on the modified object, which triggered LocalFileBackend.commit_object() / CouchDBBackend.commit_object() to write the updated object back to storage. No replacement mechanism was introduced, so the write-back is now silently dropped.

We did not notice this, since the change is reflected in the WeakValueDictionary and we did not have a unittest for this (standard server) case. However, once this reference is garbage collected, it becomes an issue.

To reproduce, you need to empty the WeakValueDictionary (or simply read the JSON file):

import gc, shutil, tempfile
from basyx.aas import model
from basyx.aas.backend.local_file import LocalFileIdentifiableStore

store_dir = tempfile.mkdtemp()
store = LocalFileIdentifiableStore(store_dir)

submodel = model.Submodel(
    id_='https://example.org/MutationTest',
    submodel_element={
        model.Property(id_short='Prop', value_type=model.datatypes.String, value='before')
    }
)
store.add(submodel)

retrieved: model.Submodel = store.get_item('https://example.org/MutationTest')
prop: model.Property = retrieved.get_referable(['Prop'])
prop.update_from(model.Property(id_short='Prop', value_type=model.datatypes.String, value='after'))
print(f"After mutation (in-memory):  {prop.value!r}")   # 'after'

del submodel, retrieved, prop
gc.collect()  # evict WeakValueDictionary cache

fresh: model.Submodel = store.get_item('https://example.org/MutationTest')
fresh_prop: model.Property = fresh.get_referable(['Prop'])  # type: ignore[assignment]
print(f"After cache eviction (disk): {fresh_prop.value!r}")  # 'before' => bug

shutil.rmtree(store_dir)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsdkSomething to do with the `sdk` package

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions