Skip to content

Commit 47cac81

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 a4fca05 commit 47cac81

5 files changed

Lines changed: 118 additions & 7 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
@@ -733,13 +733,17 @@ async def from_appvm(
733733
if not preload and appvm.can_preload():
734734
# Not necessary to await for this event as its intent is to fill
735735
# gaps and not relevant for this run. Delay to not affect this run.
736-
asyncio.ensure_future(
737-
appvm.fire_event_async(
738-
"domain-preload-dispvm-start",
739-
reason="there is a gap",
740-
delay=5,
736+
delay = appvm.get_feat_preload_delay()
737+
if delay == -1 and appvm.get_feat_preload():
738+
pass
739+
else:
740+
asyncio.ensure_future(
741+
appvm.fire_event_async(
742+
"domain-preload-dispvm-start",
743+
reason="there is a gap",
744+
delay=max(5, delay),
745+
)
741746
)
742-
)
743747

744748
if not preload and (preload_dispvm := appvm.get_feat_preload()):
745749
dispvm = None
@@ -850,8 +854,13 @@ async def use_preload(self) -> None:
850854
)
851855
self.features["preload-dispvm-in-progress"] = False
852856
self.app.save()
857+
delay = appvm.get_feat_preload_delay()
858+
if delay == -1 and appvm.get_feat_preload():
859+
return
853860
asyncio.ensure_future(
854-
appvm.fire_event_async("domain-preload-dispvm-used", dispvm=self)
861+
appvm.fire_event_async(
862+
"domain-preload-dispvm-used", dispvm=self, delay=delay
863+
)
855864
)
856865

857866
def _preload_cleanup(self) -> None:

qubes/vm/mix/dvmtemplate.py

Lines changed: 44 additions & 0 deletions
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
"""
@@ -502,6 +526,26 @@ async def on_domain_preload_dispvm_used(
502526
]
503527
)
504528

529+
def get_feat_preload_delay(self) -> int:
530+
"""
531+
Get the ``preload-dispvm-delay`` feature as float.
532+
533+
:rtype: int
534+
"""
535+
assert isinstance(self, qubes.vm.BaseVM)
536+
feature = "preload-dispvm-delay"
537+
default = 5
538+
global_features = self.app.domains["dom0"].features
539+
global_value = global_features.get(feature)
540+
value = self.features.get(feature)
541+
if value is None:
542+
if global_value is None:
543+
value = default
544+
else:
545+
value = global_value
546+
value = float(value or 0)
547+
return value
548+
505549
def get_feat_preload_threshold(self) -> int:
506550
"""
507551
Get the ``preload-dispvm-threshold`` feature as int (bytes unit).

0 commit comments

Comments
 (0)