1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-07 14:25:37 +00:00

Resolve "Memory leak if the table contains a link_row field"

This commit is contained in:
Bram Wiepjes 2021-04-15 14:55:49 +00:00
parent 4900921035
commit 8b56206bf4
6 changed files with 47 additions and 7 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database/field
changelog.md
web-frontend/modules/database/components
table
view/grid/fields

View file

@ -14,7 +14,7 @@ class DatabaseConfig(AppConfig):
will be registered to the apps, but we do not always want that to happen
because models with the same class name can differ. They are also meant to be
temporary. Removing the model from the cache does not work because if there
are multiple requests at the same it is not removed from the cache on time
are multiple requests at the same, it is not removed from the cache on time
which could result in hard failures. It is also hard to extend the
django.apps.registry.apps so this hack extends the original `register_model`
method and it will only call the original `register_model` method if the
@ -24,13 +24,20 @@ class DatabaseConfig(AppConfig):
am happy to hear about it! :)
"""
original = self.apps.register_model
original_register_model = self.apps.register_model
def register_model(app_label, model):
if not hasattr(model, "_generated_table_model") and not hasattr(
model._meta.auto_created, "_generated_table_model"
):
return original(app_label, model)
original_register_model(app_label, model)
else:
# Trigger the pending operations because the original register_model
# method also triggers them. Not triggering them can cause a memory
# leak because everytime a table model is generated, it will register
# new pending operations.
self.apps.do_pending_operations(model)
self.apps.clear_cache()
self.apps.register_model = register_model

View file

@ -555,7 +555,6 @@ class LinkRowFieldType(FieldType):
# Note that the through model will not be registered with the apps because of
# the `DatabaseConfig.prevent_generated_model_for_registering` hack.
models.ManyToManyField(
to=related_model,
related_name=related_name,
@ -565,8 +564,17 @@ class LinkRowFieldType(FieldType):
db_constraint=False,
).contribute_to_class(model, field_name)
# Trigger the newly created pending operations of all the models related to the
# created ManyToManyField. They need to be called manually because normally
# they are triggered when a new new model is registered. Not triggering them
# can cause a memory leak because everytime a table model is generated, it will
# register new pending operations.
apps = model._meta.apps
model_field = model._meta.get_field(field_name)
model_field.do_related_class(model_field.remote_field.model, None)
apps.do_pending_operations(model)
apps.do_pending_operations(related_model)
apps.do_pending_operations(model_field.remote_field.through)
apps.clear_cache()
def prepare_values(self, values, user):
"""

View file

@ -4,6 +4,7 @@ from rest_framework.status import HTTP_200_OK, HTTP_204_NO_CONTENT, HTTP_400_BAD
from django.shortcuts import reverse
from django.db import connections
from django.apps.registry import apps
from baserow.core.handler import CoreHandler
from baserow.contrib.database.fields.models import Field
@ -16,6 +17,29 @@ from baserow.contrib.database.fields.exceptions import (
from baserow.contrib.database.rows.handler import RowHandler
@pytest.mark.django_db
def test_call_apps_registry_pending_operations(data_fixture):
user = data_fixture.create_user()
database = data_fixture.create_database_application(user=user, name="Placeholder")
table = data_fixture.create_database_table(name="Example", database=database)
customers_table = data_fixture.create_database_table(
name="Customers", database=database
)
field_handler = FieldHandler()
field_handler.create_field(
user=user,
table=table,
type_name="link_row",
name="Test",
link_row_table=customers_table,
)
table.get_model()
# Make sure that there are no pending operations in the app registry. Because a
# Django ManyToManyField registers pending operations every time a table model is
# generated, which can causes a memory leak if they are not triggered.
assert len(apps._pending_operations) == 0
@pytest.mark.django_db
def test_link_row_field_type(data_fixture):
user = data_fixture.create_user()

View file

@ -2,6 +2,7 @@
## Unreleased
* Fixed memory leak in the `link_row` field.
* Switch to using a celery based email backend by default.
* Added `--add-columns` flag to the `fill_table` management command. It creates all the
field types before filling the table with random data.

View file

@ -143,8 +143,8 @@ export default {
required: true,
},
view: {
type: Object,
validator: (prop) => typeof prop === 'object' || prop === undefined,
required: true,
},
tableLoading: {
type: Boolean,

View file

@ -55,7 +55,7 @@
</li>
</ul>
<UserFilesModal
v-if="Array.isArray(value) && !this.readOnly"
v-if="Array.isArray(value) && !readOnly"
ref="uploadModal"
@uploaded="addFiles(value, $event)"
@hidden="hideModal"