Skip to content

Commit 43e8b98

Browse files
committed
Add ability to delay preload after usage
In case of making quick bursts that do not exceed the maximum number of preloaded disposables, it is faster to not preload in the meantime. These are the behaviors: - "-1": preload after X seconds only if list is empty; - "": use global value, and if it is not set, use default; - "0": preload instantaneously; and - "1": preload after X seconds. For: QubesOS/qubes-issues#10230 For: QubesOS/qubes-issues#1512
1 parent 9817509 commit 43e8b98

5 files changed

Lines changed: 110 additions & 8 deletions

File tree

qubes/tests/vm/adminvm.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,14 @@ def test_802_preload_set_threshold(self):
290290
for value in cases_valid:
291291
with self.subTest(value=value):
292292
self.vm.features["preload-dispvm-threshold"] = value
293+
294+
def test_803_preload_set_delay(self):
295+
cases_valid = ["", "0", "1", "-1", "3.14"]
296+
cases_invalid = ["a", ".2.", "1 1"]
297+
for value in cases_invalid:
298+
with self.subTest(value=value):
299+
with self.assertRaises(qubes.exc.QubesValueError):
300+
self.vm.features["preload-dispvm-delay"] = value
301+
for value in cases_valid:
302+
with self.subTest(value=value):
303+
self.vm.features["preload-dispvm-delay"] = value

qubes/tests/vm/mix/dvmtemplate.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,29 @@ def cleanup_dispvm(self):
119119
async def mock_coro(self, *args, **kwargs):
120120
pass
121121

122+
def test_010_dvm_preload_get_delay(self):
123+
cases = [
124+
(None, 0),
125+
(False, 0),
126+
("0", 0),
127+
("2", 2),
128+
("10000", 10000),
129+
("-1", -1),
130+
("-3.14", -3.14),
131+
]
132+
self.assertEqual(self.appvm.get_feat_preload_max(), 0)
133+
for value, expected_value in cases:
134+
with self.subTest(value=value, expected_value=expected_value):
135+
self.appvm.features["preload-dispvm-delay"] = value
136+
self.assertEqual(
137+
self.appvm.get_feat_preload_delay(), expected_value
138+
)
139+
cases_invalid = ["a", ".2.", "1 1"]
140+
for value in cases_invalid:
141+
with self.subTest(value=value):
142+
with self.assertRaises(qubes.exc.QubesValueError):
143+
self.appvm.features["preload-dispvm-delay"] = value
144+
122145
def test_010_dvm_preload_get_max(self):
123146
cases = [
124147
(None, 0),

qubes/vm/adminvm.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,30 @@ def on_feature_pre_set_preload_dispvm_threshold(
378378
"Invalid preload-dispvm-threshold value: not a digit"
379379
)
380380

381+
@qubes.events.handler("domain-feature-pre-set:preload-dispvm-delay")
382+
def on_feature_pre_set_preload_dispvm_delay(
383+
self, event, feature, value, oldvalue=None
384+
):
385+
"""
386+
Before accepting the ``preload-dispvm-delay`` feature, validate it.
387+
388+
:param str event: Event which was fired.
389+
:param str feature: Feature name.
390+
:param int value: New value of the feature.
391+
:param int oldvalue: Old value of the feature.
392+
"""
393+
# pylint: disable=unused-argument
394+
if value == oldvalue:
395+
return
396+
if not value:
397+
value = "0"
398+
try:
399+
float(value)
400+
except ValueError:
401+
raise qubes.exc.QubesValueError(
402+
"Invalid preload-dispvm-delay value: not an integer or float"
403+
)
404+
381405
@qubes.events.handler("domain-feature-delete:preload-dispvm-max")
382406
def on_feature_delete_preload_dispvm_max(self, event, feature):
383407
"""

qubes/vm/dispvm.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -734,13 +734,17 @@ async def from_appvm(
734734
if not preload and appvm.can_preload():
735735
# Not necessary to await for this event as its intent is to fill
736736
# gaps and not relevant for this run. Delay to not affect this run.
737-
asyncio.ensure_future(
738-
appvm.fire_event_async(
739-
"domain-preload-dispvm-start",
740-
reason="there is a gap",
741-
delay=5,
737+
delay = appvm.get_feat_preload_delay()
738+
if delay < 0 and appvm.get_feat_preload():
739+
pass
740+
else:
741+
asyncio.ensure_future(
742+
appvm.fire_event_async(
743+
"domain-preload-dispvm-start",
744+
reason="there is a gap",
745+
delay=max(5, delay),
746+
)
742747
)
743-
)
744748

745749
if not preload and (preload_dispvm := appvm.get_feat_preload()):
746750
dispvm = None
@@ -851,8 +855,13 @@ async def use_preload(self) -> None:
851855
)
852856
self.features["preload-dispvm-in-progress"] = False
853857
self.app.save()
858+
delay = appvm.get_feat_preload_delay()
859+
if delay < 0 and appvm.get_feat_preload():
860+
return
854861
asyncio.ensure_future(
855-
appvm.fire_event_async("domain-preload-dispvm-used", dispvm=self)
862+
appvm.fire_event_async(
863+
"domain-preload-dispvm-used", dispvm=self, delay=delay
864+
)
856865
)
857866

858867
def _preload_cleanup(self) -> None:

qubes/vm/mix/dvmtemplate.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,30 @@ async def on_dvmtemplate_domain_shutdown(self, _event, **_kwargs) -> None:
150150
"""
151151
await self.refresh_preload()
152152

153+
@qubes.events.handler("domain-feature-pre-set:preload-dispvm-delay")
154+
def on_feature_pre_set_preload_dispvm_delay(
155+
self, event, feature, value, oldvalue=None
156+
):
157+
"""
158+
Before accepting the ``preload-dispvm-delay`` feature, validate it.
159+
160+
:param str event: Event which was fired.
161+
:param str feature: Feature name.
162+
:param int value: New value of the feature.
163+
:param int oldvalue: Old value of the feature.
164+
"""
165+
# pylint: disable=unused-argument
166+
if value == oldvalue:
167+
return
168+
if not value:
169+
value = "0"
170+
try:
171+
float(value)
172+
except ValueError:
173+
raise qubes.exc.QubesValueError(
174+
"Invalid preload-dispvm-delay value: not an integer or float"
175+
)
176+
153177
@qubes.events.handler("domain-feature-delete:preload-dispvm-max")
154178
def on_feature_delete_preload_dispvm_max(self, event, feature) -> None:
155179
"""
@@ -454,7 +478,7 @@ async def on_domain_preload_dispvm_used(
454478
"check if template is outdated" % ", ".join(missing_services)
455479
)
456480
if delay:
457-
await asyncio.sleep(delay)
481+
await asyncio.sleep(abs(delay))
458482

459483
if event == "autostart":
460484
self.remove_preload_excess(0, reason="event autostart was called")
@@ -502,6 +526,17 @@ async def on_domain_preload_dispvm_used(
502526
]
503527
)
504528

529+
def get_feat_preload_delay(self) -> float:
530+
"""
531+
Get the ``preload-dispvm-delay`` feature as float.
532+
533+
:rtype: int
534+
"""
535+
assert isinstance(self, qubes.vm.BaseVM)
536+
value = self.features.check_with_adminvm("preload-dispvm-delay", 5)
537+
value = float(value or 0)
538+
return value
539+
505540
def get_feat_preload_threshold(self) -> int:
506541
"""
507542
Get the ``preload-dispvm-threshold`` feature as int (bytes unit).

0 commit comments

Comments
 (0)