Skip to content
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

Improved robustness of concurrent schema updates #3308

Merged
merged 3 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fiftyone/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3370,9 +3370,9 @@ def _save_field(self, field):

path, is_frame_field = self._handle_frame_field(field.path)
if is_frame_field:
field_doc = self._frame_doc_cls._get_field_doc(path)
field_doc = self._frame_doc_cls._get_field_doc(path, reload=True)
else:
field_doc = self._sample_doc_cls._get_field_doc(path)
field_doc = self._sample_doc_cls._get_field_doc(path, reload=True)

field_doc.description = field.description
field_doc.info = field.info
Expand Down
8 changes: 8 additions & 0 deletions fiftyone/core/odm/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,14 @@ class Document(BaseDocument, mongoengine.Document):
def _doc_name(cls):
return "Document"

def reload(self, *fields, **kwargs):
"""Reloads the document from the database.
Args:
*fields: an optional args list of specific fields to reload
"""
super().reload(*fields, **kwargs)

def save(
self,
upsert=False,
Expand Down
75 changes: 58 additions & 17 deletions fiftyone/core/odm/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ def merge_field_schema(
existing field of the same name or a new field is found but
``expand_schema == False``
"""
dataset = cls._dataset
dataset_doc = dataset._doc

new_schema = {}

for path, field in schema.items():
Expand All @@ -245,6 +248,10 @@ def merge_field_schema(
if not new_schema:
return False

# This fixes https://github.com/voxel51/fiftyone/issues/3185
# @todo improve list field updates in general so this isn't necessary
cls._reload_fields()

for path, field in new_schema.items():
# Special syntax for declaring the subfield of a ListField
if path.endswith("[]"):
Expand All @@ -253,7 +260,7 @@ def merge_field_schema(

cls._add_field_schema(path, field)

cls._dataset.save()
dataset_doc.save()

return True

Expand Down Expand Up @@ -462,6 +469,11 @@ def _rename_fields(cls, sample_collection, paths, new_paths):
if field is not None:
schema_paths.append((path, new_path))

# This fixes https://github.com/voxel51/fiftyone/issues/3185
# @todo improve list field updates in general so this isn't necessary
if schema_paths:
cls._reload_fields()

if simple_paths:
_paths, _new_paths = zip(*simple_paths)
cls._rename_fields_simple(_paths, _new_paths)
Expand All @@ -483,7 +495,7 @@ def _rename_fields(cls, sample_collection, paths, new_paths):
new_paths = [dataset._FRAMES_PREFIX + p for p in new_paths]

dataset_doc.app_config._rename_paths(paths, new_paths)
dataset.save()
dataset_doc.save()

@classmethod
def _clone_fields(cls, sample_collection, paths, new_paths):
Expand Down Expand Up @@ -542,6 +554,11 @@ def _clone_fields(cls, sample_collection, paths, new_paths):
if field is not None:
schema_paths.append((path, new_path))

# This fixes https://github.com/voxel51/fiftyone/issues/3185
# @todo improve list field updates in general so this isn't necessary
if schema_paths:
cls._reload_fields()

if simple_paths:
_paths, _new_paths = zip(*simple_paths)
cls._clone_fields_simple(_paths, _new_paths)
Expand All @@ -553,7 +570,7 @@ def _clone_fields(cls, sample_collection, paths, new_paths):
for path, new_path in schema_paths:
cls._clone_field_schema(path, new_path)

dataset.save()
dataset_doc.save()

@classmethod
def _clear_fields(cls, sample_collection, paths):
Expand Down Expand Up @@ -664,18 +681,24 @@ def _delete_fields(cls, paths, error_level=0):
del_paths.append(path)
del_schema_paths.append(path)

if del_paths:
cls._delete_fields_simple(del_paths)
if not del_paths:
return

# This fixes https://github.com/voxel51/fiftyone/issues/3185
# @todo improve list field updates in general so this isn't necessary
if del_schema_paths:
cls._reload_fields()

cls._delete_fields_simple(del_paths)

for del_path in del_schema_paths:
cls._delete_field_schema(del_path)

if del_paths:
if is_frame_field:
del_paths = [dataset._FRAMES_PREFIX + p for p in del_paths]
if is_frame_field:
del_paths = [dataset._FRAMES_PREFIX + p for p in del_paths]

dataset_doc.app_config._delete_paths(del_paths)
dataset.save()
dataset_doc.app_config._delete_paths(del_paths)
dataset_doc.save()

@classmethod
def _remove_dynamic_fields(cls, paths, error_level=0):
Expand All @@ -691,6 +714,9 @@ def _remove_dynamic_fields(cls, paths, error_level=0):
- 1: log warning if a field cannot be removed
- 2: ignore fields that cannot be removed
"""
dataset = cls._dataset
dataset_doc = dataset._doc

del_paths = []

for path in paths:
Expand Down Expand Up @@ -730,16 +756,21 @@ def _remove_dynamic_fields(cls, paths, error_level=0):

del_paths.append(path)

if not del_paths:
return

# This fixes https://github.com/voxel51/fiftyone/issues/3185
# @todo improve list field updates in general so this isn't necessary
cls._reload_fields()

for del_path in del_paths:
cls._delete_field_schema(del_path)

if del_paths:
dataset = cls._dataset
if cls._is_frames_doc:
del_paths = [dataset._FRAMES_PREFIX + p for p in del_paths]
if cls._is_frames_doc:
del_paths = [dataset._FRAMES_PREFIX + p for p in del_paths]

dataset._doc.app_config._delete_paths(del_paths)
dataset.save()
dataset_doc.app_config._delete_paths(del_paths)
dataset_doc.save()

@classmethod
def _rename_fields_simple(cls, paths, new_paths):
Expand Down Expand Up @@ -1087,6 +1118,11 @@ def _delete_field_schema(cls, path):
doc._undeclare_field(field_name)
_delete_field_doc(field_docs, field_name)

@classmethod
def _reload_fields(cls):
dataset_doc = cls._dataset._doc
dataset_doc.reload(cls._fields_attr())

@classmethod
def _get_field(cls, path, allow_missing=False, check_default=False):
chunks = path.split(".")
Expand Down Expand Up @@ -1116,7 +1152,12 @@ def _get_field(cls, path, allow_missing=False, check_default=False):
return field, is_default

@classmethod
def _get_field_doc(cls, path, allow_missing=False):
def _get_field_doc(cls, path, allow_missing=False, reload=False):
# This fixes https://github.com/voxel51/fiftyone/issues/3185
# @todo improve list field updates in general so this isn't necessary
if reload:
cls._reload_fields()

chunks = path.split(".")
field_docs = cls._dataset._doc[cls._fields_attr()]

Expand Down