Skip to content

Commit a7265e0

Browse files
committed
scheduler: add diagnostic messages for SCHED_DEADLINE
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1 parent ff8f451 commit a7265e0

File tree

3 files changed

+277
-3
lines changed

3 files changed

+277
-3
lines changed

src/libcrun/scheduler.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,38 @@ libcrun_reset_cpu_affinity_mask (pid_t pid, libcrun_error_t *err)
9696
return 0;
9797
}
9898

99+
static int
100+
diagnose_scheduler_failure (int ret, libcrun_error_t *err, runtime_spec_schema_config_schema_process *process,
101+
struct sched_attr_s *attr)
102+
{
103+
if (attr->sched_policy == SCHED_DEADLINE && errno == EINVAL)
104+
{
105+
if (! process->scheduler->runtime_present)
106+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` requires `runtime`");
107+
if (! process->scheduler->deadline_present)
108+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` requires `deadline`");
109+
110+
if (attr->sched_runtime == 0)
111+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` runtime must be greater than 0");
112+
if (attr->sched_deadline == 0)
113+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` deadline must be greater than 0");
114+
if (attr->sched_period == 0)
115+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` period must be greater than 0");
116+
117+
if (attr->sched_runtime > attr->sched_deadline)
118+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` runtime (%" PRIu64 ") must be <= deadline (%" PRIu64 ")",
119+
attr->sched_runtime, attr->sched_deadline);
120+
if (attr->sched_deadline > attr->sched_period)
121+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` deadline (%" PRIu64 ") must be <= period (%" PRIu64 ")",
122+
attr->sched_deadline, attr->sched_period);
123+
124+
return crun_make_error (err, errno, "sched_setattr: invalid `SCHED_DEADLINE` parameters (runtime=%" PRIu64 ", deadline=%" PRIu64 ", period=%" PRIu64 ")",
125+
attr->sched_runtime, attr->sched_deadline, attr->sched_period);
126+
}
127+
128+
return crun_make_error (err, errno, "sched_setattr");
129+
}
130+
99131
int
100132
libcrun_set_scheduler (pid_t pid, runtime_spec_schema_config_schema_process *process, libcrun_error_t *err)
101133
{
@@ -176,7 +208,7 @@ libcrun_set_scheduler (pid_t pid, runtime_spec_schema_config_schema_process *pro
176208

177209
ret = syscall_sched_setattr (pid, &attr, 0);
178210
if (UNLIKELY (ret < 0))
179-
return crun_make_error (err, errno, "sched_setattr");
211+
return diagnose_scheduler_failure (ret, err, process, &attr);
180212

181213
return 0;
182214
}

tests/test_scheduler.py

Lines changed: 239 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,11 @@ def test_scheduler_nice_value():
194194

195195

196196
def test_scheduler_deadline():
197-
"""Test SCHED_DEADLINE scheduler policy."""
197+
"""Test SCHED_DEADLINE scheduler policy with all parameters (working case)."""
198198
if is_rootless():
199199
return (77, "SCHED_DEADLINE requires root")
200+
if not is_sched_deadline_available():
201+
return (77, "SCHED_DEADLINE not available in kernel")
200202

201203
conf = base_config()
202204
add_all_namespaces(conf)
@@ -230,6 +232,44 @@ def test_scheduler_deadline():
230232
return -1
231233

232234

235+
def test_scheduler_deadline_no_period():
236+
"""Test SCHED_DEADLINE scheduler policy without period (should work - kernel allows it)."""
237+
if is_rootless():
238+
return (77, "SCHED_DEADLINE requires root")
239+
if not is_sched_deadline_available():
240+
return (77, "SCHED_DEADLINE not available in kernel")
241+
242+
conf = base_config()
243+
add_all_namespaces(conf)
244+
245+
# SCHED_DEADLINE with runtime and deadline but no period
246+
# Kernel should accept this and use deadline as period
247+
conf['process']['scheduler'] = {
248+
'policy': 'SCHED_DEADLINE',
249+
'runtime': 10000000, # 10ms
250+
'deadline': 20000000 # 20ms, no period
251+
}
252+
253+
conf['process']['args'] = ['/init', 'true']
254+
255+
try:
256+
out, _ = run_and_get_output(conf, hide_stderr=True)
257+
return 0 # Should succeed
258+
259+
except subprocess.CalledProcessError as e:
260+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
261+
if "scheduler" in output.lower() or "permission" in output.lower() or "deadline" in output.lower():
262+
return (77, "SCHED_DEADLINE not available")
263+
logger.info("test failed: %s", e)
264+
return -1
265+
except Exception as e:
266+
error_str = str(e).lower()
267+
if "deadline" in error_str or "scheduler" in error_str:
268+
return (77, "SCHED_DEADLINE not available")
269+
logger.info("test failed: %s", e)
270+
return -1
271+
272+
233273
def test_scheduler_flags():
234274
"""Test scheduler with flags (reset_on_fork)."""
235275
if is_rootless():
@@ -262,6 +302,198 @@ def test_scheduler_flags():
262302
return -1
263303

264304

305+
def test_scheduler_deadline_missing_runtime():
306+
"""Test SCHED_DEADLINE validation - missing runtime parameter."""
307+
if is_rootless():
308+
return (77, "SCHED_DEADLINE requires root")
309+
if not is_sched_deadline_available():
310+
return (77, "SCHED_DEADLINE not available in kernel")
311+
312+
conf = base_config()
313+
add_all_namespaces(conf)
314+
315+
# Missing runtime parameter
316+
conf['process']['scheduler'] = {
317+
'policy': 'SCHED_DEADLINE',
318+
'deadline': 20000000, # 20ms
319+
'period': 20000000 # 20ms
320+
}
321+
322+
conf['process']['args'] = ['/init', 'true']
323+
324+
try:
325+
out, _ = run_and_get_output(conf, hide_stderr=False)
326+
# Should have failed due to missing runtime
327+
return -1
328+
329+
except subprocess.CalledProcessError as e:
330+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
331+
if "sched_setattr: `SCHED_DEADLINE` requires `runtime`" in output:
332+
return 0 # Expected validation error
333+
if "scheduler" in output.lower() or "deadline" in output.lower():
334+
return (77, "SCHED_DEADLINE not available")
335+
logger.info("unexpected error: %s", output)
336+
return -1
337+
except Exception as e:
338+
logger.info("test failed: %s", e)
339+
return -1
340+
341+
342+
def test_scheduler_deadline_missing_deadline():
343+
"""Test SCHED_DEADLINE validation - missing deadline parameter."""
344+
if is_rootless():
345+
return (77, "SCHED_DEADLINE requires root")
346+
if not is_sched_deadline_available():
347+
return (77, "SCHED_DEADLINE not available in kernel")
348+
349+
conf = base_config()
350+
add_all_namespaces(conf)
351+
352+
# Missing deadline parameter
353+
conf['process']['scheduler'] = {
354+
'policy': 'SCHED_DEADLINE',
355+
'runtime': 10000000, # 10ms
356+
'period': 20000000 # 20ms
357+
}
358+
359+
conf['process']['args'] = ['/init', 'true']
360+
361+
try:
362+
out, _ = run_and_get_output(conf, hide_stderr=False)
363+
# Should have failed due to missing deadline
364+
return -1
365+
366+
except subprocess.CalledProcessError as e:
367+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
368+
if "sched_setattr: `SCHED_DEADLINE` requires `deadline`" in output:
369+
return 0 # Expected validation error
370+
if "scheduler" in output.lower() or "deadline" in output.lower():
371+
return (77, "SCHED_DEADLINE not available")
372+
logger.info("unexpected error: %s", output)
373+
return -1
374+
except Exception as e:
375+
logger.info("test failed: %s", e)
376+
return -1
377+
378+
379+
380+
381+
def test_scheduler_deadline_zero_runtime():
382+
"""Test SCHED_DEADLINE validation - zero runtime."""
383+
if is_rootless():
384+
return (77, "SCHED_DEADLINE requires root")
385+
if not is_sched_deadline_available():
386+
return (77, "SCHED_DEADLINE not available in kernel")
387+
388+
conf = base_config()
389+
add_all_namespaces(conf)
390+
391+
# Zero runtime
392+
conf['process']['scheduler'] = {
393+
'policy': 'SCHED_DEADLINE',
394+
'runtime': 0,
395+
'deadline': 20000000, # 20ms
396+
'period': 20000000 # 20ms
397+
}
398+
399+
conf['process']['args'] = ['/init', 'true']
400+
401+
try:
402+
out, _ = run_and_get_output(conf, hide_stderr=False)
403+
# Should have failed due to zero runtime
404+
return -1
405+
406+
except subprocess.CalledProcessError as e:
407+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
408+
if "sched_setattr: `SCHED_DEADLINE` runtime must be greater than 0" in output:
409+
return 0 # Expected validation error
410+
if "scheduler" in output.lower() or "deadline" in output.lower():
411+
return (77, "SCHED_DEADLINE not available")
412+
logger.info("unexpected error: %s", output)
413+
return -1
414+
except Exception as e:
415+
logger.info("test failed: %s", e)
416+
return -1
417+
418+
419+
def test_scheduler_deadline_invalid_order():
420+
"""Test SCHED_DEADLINE validation - runtime > deadline."""
421+
if is_rootless():
422+
return (77, "SCHED_DEADLINE requires root")
423+
if not is_sched_deadline_available():
424+
return (77, "SCHED_DEADLINE not available in kernel")
425+
426+
conf = base_config()
427+
add_all_namespaces(conf)
428+
429+
# runtime > deadline (invalid)
430+
conf['process']['scheduler'] = {
431+
'policy': 'SCHED_DEADLINE',
432+
'runtime': 30000000, # 30ms
433+
'deadline': 20000000, # 20ms
434+
'period': 40000000 # 40ms
435+
}
436+
437+
conf['process']['args'] = ['/init', 'true']
438+
439+
try:
440+
out, _ = run_and_get_output(conf, hide_stderr=False)
441+
# Should have failed due to invalid order
442+
return -1
443+
444+
except subprocess.CalledProcessError as e:
445+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
446+
if "sched_setattr: `SCHED_DEADLINE` runtime" in output and "must be <=" in output and "deadline" in output:
447+
return 0 # Expected validation error
448+
if "scheduler" in output.lower() or "deadline" in output.lower():
449+
return (77, "SCHED_DEADLINE not available")
450+
logger.info("unexpected error: %s", output)
451+
return -1
452+
except Exception as e:
453+
logger.info("test failed: %s", e)
454+
return -1
455+
456+
457+
def test_scheduler_deadline_invalid_deadline_period():
458+
"""Test SCHED_DEADLINE validation - deadline > period."""
459+
if is_rootless():
460+
return (77, "SCHED_DEADLINE requires root")
461+
if not is_sched_deadline_available():
462+
return (77, "SCHED_DEADLINE not available in kernel")
463+
464+
conf = base_config()
465+
add_all_namespaces(conf)
466+
467+
# deadline > period (invalid)
468+
conf['process']['scheduler'] = {
469+
'policy': 'SCHED_DEADLINE',
470+
'runtime': 10000000, # 10ms
471+
'deadline': 30000000, # 30ms
472+
'period': 20000000 # 20ms
473+
}
474+
475+
conf['process']['args'] = ['/init', 'true']
476+
477+
try:
478+
out, _ = run_and_get_output(conf, hide_stderr=False)
479+
# Should have failed due to invalid order
480+
return -1
481+
482+
except subprocess.CalledProcessError as e:
483+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
484+
if "sched_setattr: `SCHED_DEADLINE` deadline" in output and "must be <=" in output and "period" in output:
485+
return 0 # Expected validation error
486+
if "scheduler" in output.lower() or "deadline" in output.lower():
487+
return (77, "SCHED_DEADLINE not available")
488+
logger.info("unexpected error: %s", output)
489+
return -1
490+
except Exception as e:
491+
logger.info("test failed: %s", e)
492+
return -1
493+
494+
495+
496+
265497
all_tests = {
266498
"scheduler-fifo": test_scheduler_fifo,
267499
"scheduler-rr": test_scheduler_rr,
@@ -270,7 +502,13 @@ def test_scheduler_flags():
270502
"scheduler-other": test_scheduler_other,
271503
"scheduler-nice-value": test_scheduler_nice_value,
272504
"scheduler-deadline": test_scheduler_deadline,
505+
"scheduler-deadline-no-period": test_scheduler_deadline_no_period,
273506
"scheduler-flags": test_scheduler_flags,
507+
"scheduler-deadline-missing-runtime": test_scheduler_deadline_missing_runtime,
508+
"scheduler-deadline-missing-deadline": test_scheduler_deadline_missing_deadline,
509+
"scheduler-deadline-zero-runtime": test_scheduler_deadline_zero_runtime,
510+
"scheduler-deadline-invalid-order": test_scheduler_deadline_invalid_order,
511+
"scheduler-deadline-invalid-deadline-period": test_scheduler_deadline_invalid_deadline_period,
274512
}
275513

276514
if __name__ == "__main__":

tests/tests_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
# Export logger for use in test files
3939
__all__ = ['logger', 'base_config', 'run_and_get_output', 'run_crun_command', 'run_crun_command_raw',
4040
'parse_proc_status', 'add_all_namespaces', 'tests_main', 'is_rootless',
41-
'is_cgroup_v2_unified', 'get_crun_feature_string', 'running_on_systemd',
41+
'is_cgroup_v2_unified', 'is_sched_deadline_available', 'get_crun_feature_string', 'running_on_systemd',
4242
'get_tests_root', 'get_tests_root_status', 'get_init_path', 'get_crun_path',
4343
'get_cgroup_manager', 'get_test_environment']
4444

@@ -484,6 +484,10 @@ def is_rootless():
484484
def is_cgroup_v2_unified():
485485
return subprocess.check_output("stat -c%T -f /sys/fs/cgroup".split()).decode("utf-8").strip() == "cgroup2fs"
486486

487+
def is_sched_deadline_available():
488+
"""Check if SCHED_DEADLINE is available in the kernel."""
489+
return os.path.exists("/proc/sys/kernel/sched_deadline_period_max_us")
490+
487491
def get_crun_feature_string():
488492
for i in run_crun_command(['--version']).split('\n'):
489493
if i.startswith('+'):

0 commit comments

Comments
 (0)