Skip to content

Commit ed0d4e2

Browse files
committed
[FIX] base: improve session existence check mechanism
The commit: odoo@6fb676a adds the model `res.device.log` which will hold a lot of data. In order to use this data efficiently, we decide to process data with the boolean field `revoked` equal to `True` (via the indexes). This boolean field indicates whether the session that generated the log is still present on the disk. The commit: odoo@e4c9d17 adds an automatic verification mechanism to change this value if necessary. Between the time the model was created and the time the verification mechanism was implemented, the table may have become too large. This will result in a very long write within a transaction. The purpose of this commit is to correct the method for performing the batch writing. closes odoo#232126 X-original-commit: e461c78 Signed-off-by: Denis Ledoux (dle) <[email protected]> Signed-off-by: Thomas Lefebvre (thle) <[email protected]>
1 parent 9b3157b commit ed0d4e2

File tree

3 files changed

+44
-67
lines changed

3 files changed

+44
-67
lines changed

odoo/addons/base/models/res_device.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Part of Odoo. See LICENSE file for full copyright and licensing details.
22

33
from contextlib import nullcontext
4-
from datetime import datetime
4+
from datetime import datetime, timedelta
55
import logging
66

77
from odoo import api, fields, models, tools
8-
from odoo.http import GeoIP, request, root, STORED_SESSION_BYTES
8+
from odoo.http import GeoIP, get_session_max_inactivity, request, root, STORED_SESSION_BYTES
99
from odoo.tools import SQL, OrderedSet, unique
1010
from odoo.tools.translate import _
1111
from .res_users import check_identity
@@ -137,24 +137,34 @@ def __update_revoked(self):
137137
Set the field ``revoked`` to ``True`` for ``res.device.log``
138138
for which the session file no longer exists on the filesystem.
139139
"""
140-
device_logs_by_session_identifier = {}
141-
for session_identifier, device_logs in self.env['res.device.log']._read_group(
142-
domain=[('revoked', '=', False)],
143-
groupby=['session_identifier'],
144-
aggregates=['id:recordset'],
145-
):
146-
device_logs_by_session_identifier[session_identifier] = device_logs
147-
148-
revoked_session_identifiers = root.session_store.get_missing_session_identifiers(
149-
device_logs_by_session_identifier.keys()
150-
)
151-
device_logs_to_revoke = self.env['res.device.log'].concat(*map(
152-
device_logs_by_session_identifier.get,
153-
revoked_session_identifiers
154-
))
155-
# Initial run may take 5-10 minutes due to many non-revoked sessions,
156-
# marking them enables index use on ``revoked IS NOT TRUE``.
157-
device_logs_to_revoke.sudo().write({'revoked': True})
140+
batch_size = 100_000
141+
offset = 0
142+
143+
while True:
144+
candidate_device_log_ids = self.env['res.device.log'].search_fetch(
145+
[
146+
('revoked', '=', False),
147+
('last_activity', '<', datetime.now() - timedelta(seconds=get_session_max_inactivity(self.env))),
148+
],
149+
['session_identifier'],
150+
order='id',
151+
limit=batch_size,
152+
offset=offset,
153+
)
154+
if not candidate_device_log_ids:
155+
break
156+
offset += batch_size
157+
revoked_session_identifiers = root.session_store.get_missing_session_identifiers(
158+
set(candidate_device_log_ids.mapped('session_identifier'))
159+
)
160+
if not revoked_session_identifiers:
161+
continue
162+
to_revoke = candidate_device_log_ids.filtered(
163+
lambda candidate: candidate.session_identifier in revoked_session_identifiers
164+
)
165+
to_revoke.write({'revoked': True})
166+
self.env.cr.commit()
167+
offset -= len(to_revoke)
158168

159169

160170
class ResDevice(models.Model):

odoo/addons/test_http/tests/test_device.py

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Part of Odoo. See LICENSE file for full copyright and licensing details.
22

3+
from datetime import datetime
34
from freezegun import freeze_time
45
from unittest.mock import patch
56

@@ -354,56 +355,37 @@ def _create_device_log_for_user(self, session, count):
354355
'session_identifier': odoo.http.root.session_store.generate_key(),
355356
'user_id': session.uid,
356357
'revoked': False,
358+
'first_activity': datetime.now(),
359+
'last_activity': datetime.now(),
357360
})
358361

359-
def test_filesystem_reflexion_user(self):
362+
def test_filesystem_reflexion(self):
360363
session = self.authenticate(self.user_admin.login, self.user_admin.login)
361-
self._create_device_log_for_user(session, 10)
362-
session = self.authenticate(self.user_internal.login, self.user_internal.login)
363-
self._create_device_log_for_user(session, 10)
364-
365-
devices, logs = self.get_devices_logs(self.user_internal)
366-
self.assertEqual(len(devices), 10)
367-
self.assertEqual(len(logs), 10)
368-
self.assertEqual(len(self.user_internal.device_ids), 10)
369-
370-
self.DeviceLog.with_user(self.user_internal)._ResDeviceLog__update_revoked()
371-
self.DeviceLog.flush_model() # Because write on ``res.device.log`` and so we have new values in cache
372-
self.Device.invalidate_model() # Because it depends on the ``res.device.log`` model (updated in database)
373-
374-
devices, _ = self.get_devices_logs(self.user_internal)
375-
self.assertEqual(len(devices), 0) # No file exist on the filesystem (``revoked`` equals to ``False``)
376-
self.assertEqual(len(self.user_internal.device_ids), 0)
377-
378-
# Admin device logs are not updated
379-
devices, _ = self.get_devices_logs(self.user_admin)
380-
self.assertEqual(len(devices), 10)
381-
self.assertEqual(len(self.user_admin.device_ids), 10)
382-
383-
def test_filesystem_reflexion_admin(self):
384-
session = self.authenticate(self.user_admin.login, self.user_admin.login)
385-
self._create_device_log_for_user(session, 10)
364+
with freeze_time('2025-01-01 08:00:00'):
365+
self._create_device_log_for_user(session, 10)
386366

387367
devices, logs = self.get_devices_logs(self.user_admin)
388368
self.assertEqual(len(devices), 10)
389369
self.assertEqual(len(logs), 10)
390370
self.assertEqual(len(self.user_admin.device_ids), 10)
391371

392372
session = self.authenticate(self.user_internal.login, self.user_internal.login)
393-
self._create_device_log_for_user(session, 10)
373+
with freeze_time('2025-01-01 08:00:00'):
374+
self._create_device_log_for_user(session, 10)
394375

395376
devices, logs = self.get_devices_logs(self.user_internal)
396377
self.assertEqual(len(devices), 10)
397378
self.assertEqual(len(logs), 10)
398379
self.assertEqual(len(self.user_internal.device_ids), 10)
399380

400-
# Admin can update all device logs
401-
self.DeviceLog.with_user(self.user_admin)._ResDeviceLog__update_revoked()
402-
self.DeviceLog.flush_model()
403-
self.Device.invalidate_model()
381+
# Update all device logs
382+
with freeze_time('2025-02-01 08:00:00'), patch.object(self.cr, 'commit', lambda: ...):
383+
self.DeviceLog.sudo()._ResDeviceLog__update_revoked()
384+
self.DeviceLog.flush_model() # Because write on ``res.device.log`` and so we have new values in cache
385+
self.Device.invalidate_model() # Because it depends on the ``res.device.log`` model (updated in database)
404386

405387
devices, _ = self.get_devices_logs(self.user_admin)
406-
self.assertEqual(len(devices), 0)
388+
self.assertEqual(len(devices), 0) # No file exist on the filesystem (``revoked`` equals to ``True``)
407389
self.assertEqual(len(self.user_admin.device_ids), 0)
408390

409391
devices, _ = self.get_devices_logs(self.user_internal)

odoo/http.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,21 +1306,6 @@ def get_missing_session_identifiers(self, identifiers):
13061306
:type identifiers: iterable
13071307
:return: the identifiers which are not present on the filesystem
13081308
:rtype: set
1309-
1310-
Note 1:
1311-
Working with identifiers 42 characters long means that
1312-
we don't have to work with the entire sid session,
1313-
while maintaining sufficient entropy to avoid collisions.
1314-
See details in ``generate_key``.
1315-
1316-
Note 2:
1317-
Scans the session store for inactive (GC'd) sessions.
1318-
Works even if GC is done externally (not via ``vacuum()``).
1319-
Performance is acceptable for an infrequent background job:
1320-
- listing ``directories``: 1-5s on SSD
1321-
- iterating sessions:
1322-
- 25k on standard SSD: ~1.5 min
1323-
- 2M on RAID10 SSD: ~25s
13241309
"""
13251310
# There are a lot of session files.
13261311
# Use the param ``identifiers`` to select the necessary directories.

0 commit comments

Comments
 (0)