Skip to content

Commit e049820

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

File tree

3 files changed

+342
-12
lines changed

3 files changed

+342
-12
lines changed

src/libcrun/scheduler.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,52 @@ 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+
/* Per sched(7), ff sched_period is specified as 0, then it is made the same as sched_deadline. */
115+
116+
if (attr->sched_runtime > attr->sched_deadline)
117+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` runtime (%" PRIu64 ") must be <= deadline (%" PRIu64 ")",
118+
attr->sched_runtime, attr->sched_deadline);
119+
if (attr->sched_period != 0 && attr->sched_deadline > attr->sched_period)
120+
return crun_make_error (err, errno, "sched_setattr: `SCHED_DEADLINE` deadline (%" PRIu64 ") must be <= period (%" PRIu64 ")",
121+
attr->sched_deadline, attr->sched_period);
122+
123+
/* sched(7) says "under the current implementation, all of the parameter values
124+
* must be at least 1024 <...> and less than 2^63". */
125+
const uint64_t min = 1024;
126+
const uint64_t max = 1ULL << 63;
127+
128+
if (attr->sched_runtime < min || attr->sched_runtime > max)
129+
return crun_make_error(err, errno, "sched_setattr: `SCHED_DEADLINE` runtime (%" PRIu64 ") must be between %" PRIu64 " and %" PRIu64,
130+
attr->sched_runtime, min, max);
131+
if (attr->sched_deadline < min || attr->sched_deadline > max)
132+
return crun_make_error(err, errno, "sched_setattr: `SCHED_DEADLINE` deadline (%" PRIu64 ") must be between %" PRIu64 " and %" PRIu64,
133+
attr->sched_deadline, min, max);
134+
if (attr->sched_period != 0 && (attr->sched_period < min || attr->sched_period > max))
135+
return crun_make_error(err, errno, "sched_setattr: `SCHED_DEADLINE` period (%" PRIu64 ") must be between %" PRIu64 " and %" PRIu64,
136+
attr->sched_period, min, max);
137+
138+
return crun_make_error (err, errno, "sched_setattr: invalid `SCHED_DEADLINE` parameters (runtime=%" PRIu64 ", deadline=%" PRIu64 ", period=%" PRIu64 ")",
139+
attr->sched_runtime, attr->sched_deadline, attr->sched_period);
140+
}
141+
142+
return crun_make_error (err, errno, "sched_setattr");
143+
}
144+
99145
int
100146
libcrun_set_scheduler (pid_t pid, runtime_spec_schema_config_schema_process *process, libcrun_error_t *err)
101147
{
@@ -176,7 +222,7 @@ libcrun_set_scheduler (pid_t pid, runtime_spec_schema_config_schema_process *pro
176222

177223
ret = syscall_sched_setattr (pid, &attr, 0);
178224
if (UNLIKELY (ret < 0))
179-
return crun_make_error (err, errno, "sched_setattr");
225+
return diagnose_scheduler_failure (ret, err, process, &attr);
180226

181227
return 0;
182228
}

tests/test_scheduler.py

Lines changed: 290 additions & 10 deletions
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)
@@ -216,16 +218,41 @@ def test_scheduler_deadline():
216218
return 0
217219

218220
except subprocess.CalledProcessError as e:
219-
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
220-
# SCHED_DEADLINE often not available
221-
if "scheduler" in output.lower() or "permission" in output.lower() or "deadline" in output.lower():
222-
return (77, "SCHED_DEADLINE not available")
223221
logger.info("test failed: %s", e)
224222
return -1
225223
except Exception as e:
226-
error_str = str(e).lower()
227-
if "deadline" in error_str or "scheduler" in error_str:
228-
return (77, "SCHED_DEADLINE not available")
224+
logger.info("test failed: %s", e)
225+
return -1
226+
227+
228+
def test_scheduler_deadline_no_period():
229+
"""Test SCHED_DEADLINE scheduler policy without period (should work - kernel allows it)."""
230+
if is_rootless():
231+
return (77, "SCHED_DEADLINE requires root")
232+
if not is_sched_deadline_available():
233+
return (77, "SCHED_DEADLINE not available in kernel")
234+
235+
conf = base_config()
236+
add_all_namespaces(conf)
237+
238+
# SCHED_DEADLINE with runtime and deadline but no period
239+
# Kernel should accept this and use deadline as period
240+
conf['process']['scheduler'] = {
241+
'policy': 'SCHED_DEADLINE',
242+
'runtime': 10000000, # 10ms
243+
'deadline': 20000000 # 20ms, no period
244+
}
245+
246+
conf['process']['args'] = ['/init', 'true']
247+
248+
try:
249+
out, _ = run_and_get_output(conf, hide_stderr=True)
250+
return 0 # Should succeed
251+
252+
except subprocess.CalledProcessError as e:
253+
logger.info("test failed: %s", e)
254+
return -1
255+
except Exception as e:
229256
logger.info("test failed: %s", e)
230257
return -1
231258

@@ -251,12 +278,257 @@ def test_scheduler_flags():
251278
out, _ = run_and_get_output(conf, hide_stderr=True)
252279
return 0
253280

281+
except subprocess.CalledProcessError as e:
282+
logger.info("test failed: %s", e)
283+
return -1
284+
except Exception as e:
285+
logger.info("test failed: %s", e)
286+
return -1
287+
288+
289+
def test_scheduler_deadline_missing_runtime():
290+
"""Test SCHED_DEADLINE validation - missing runtime parameter."""
291+
if is_rootless():
292+
return (77, "SCHED_DEADLINE requires root")
293+
if not is_sched_deadline_available():
294+
return (77, "SCHED_DEADLINE not available in kernel")
295+
296+
conf = base_config()
297+
add_all_namespaces(conf)
298+
299+
# Missing runtime parameter
300+
conf['process']['scheduler'] = {
301+
'policy': 'SCHED_DEADLINE',
302+
'deadline': 20000000, # 20ms
303+
'period': 20000000 # 20ms
304+
}
305+
306+
conf['process']['args'] = ['/init', 'true']
307+
308+
try:
309+
out, _ = run_and_get_output(conf, hide_stderr=False)
310+
# Should have failed due to missing runtime
311+
return -1
312+
254313
except subprocess.CalledProcessError as e:
255314
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
256-
if "scheduler" in output.lower() or "permission" in output.lower():
257-
return (77, "scheduler flags not available")
315+
if "sched_setattr: `SCHED_DEADLINE` requires `runtime`" in output:
316+
return 0 # Expected validation error
317+
logger.info("unexpected error: %s", output)
318+
return -1
319+
except Exception as e:
258320
logger.info("test failed: %s", e)
259321
return -1
322+
323+
324+
def test_scheduler_deadline_missing_deadline():
325+
"""Test SCHED_DEADLINE validation - missing deadline parameter."""
326+
if is_rootless():
327+
return (77, "SCHED_DEADLINE requires root")
328+
if not is_sched_deadline_available():
329+
return (77, "SCHED_DEADLINE not available in kernel")
330+
331+
conf = base_config()
332+
add_all_namespaces(conf)
333+
334+
# Missing deadline parameter
335+
conf['process']['scheduler'] = {
336+
'policy': 'SCHED_DEADLINE',
337+
'runtime': 10000000, # 10ms
338+
'period': 20000000 # 20ms
339+
}
340+
341+
conf['process']['args'] = ['/init', 'true']
342+
343+
try:
344+
out, _ = run_and_get_output(conf, hide_stderr=False)
345+
# Should have failed due to missing deadline
346+
return -1
347+
348+
except subprocess.CalledProcessError as e:
349+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
350+
if "sched_setattr: `SCHED_DEADLINE` requires `deadline`" in output:
351+
return 0 # Expected validation error
352+
logger.info("unexpected error: %s", output)
353+
return -1
354+
except Exception as e:
355+
logger.info("test failed: %s", e)
356+
return -1
357+
358+
359+
360+
361+
def test_scheduler_deadline_zero_runtime():
362+
"""Test SCHED_DEADLINE validation - zero runtime."""
363+
if is_rootless():
364+
return (77, "SCHED_DEADLINE requires root")
365+
if not is_sched_deadline_available():
366+
return (77, "SCHED_DEADLINE not available in kernel")
367+
368+
conf = base_config()
369+
add_all_namespaces(conf)
370+
371+
# Zero runtime
372+
conf['process']['scheduler'] = {
373+
'policy': 'SCHED_DEADLINE',
374+
'runtime': 0,
375+
'deadline': 20000000, # 20ms
376+
'period': 20000000 # 20ms
377+
}
378+
379+
conf['process']['args'] = ['/init', 'true']
380+
381+
try:
382+
out, _ = run_and_get_output(conf, hide_stderr=False)
383+
# Should have failed due to zero runtime
384+
return -1
385+
386+
except subprocess.CalledProcessError as e:
387+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
388+
if "sched_setattr: `SCHED_DEADLINE` runtime must be greater than 0" in output:
389+
return 0 # Expected validation error
390+
logger.info("unexpected error: %s", output)
391+
return -1
392+
except Exception as e:
393+
logger.info("test failed: %s", e)
394+
return -1
395+
396+
397+
def test_scheduler_deadline_invalid_order():
398+
"""Test SCHED_DEADLINE validation - runtime > deadline."""
399+
if is_rootless():
400+
return (77, "SCHED_DEADLINE requires root")
401+
if not is_sched_deadline_available():
402+
return (77, "SCHED_DEADLINE not available in kernel")
403+
404+
conf = base_config()
405+
add_all_namespaces(conf)
406+
407+
# runtime > deadline (invalid)
408+
conf['process']['scheduler'] = {
409+
'policy': 'SCHED_DEADLINE',
410+
'runtime': 30000000, # 30ms
411+
'deadline': 20000000, # 20ms
412+
'period': 40000000 # 40ms
413+
}
414+
415+
conf['process']['args'] = ['/init', 'true']
416+
417+
try:
418+
out, _ = run_and_get_output(conf, hide_stderr=False)
419+
# Should have failed due to invalid order
420+
return -1
421+
422+
except subprocess.CalledProcessError as e:
423+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
424+
if "sched_setattr: `SCHED_DEADLINE` runtime" in output and "must be <=" in output and "deadline" in output:
425+
return 0 # Expected validation error
426+
logger.info("unexpected error: %s", output)
427+
return -1
428+
except Exception as e:
429+
logger.info("test failed: %s", e)
430+
return -1
431+
432+
433+
def test_scheduler_deadline_invalid_deadline_period():
434+
"""Test SCHED_DEADLINE validation - deadline > period."""
435+
if is_rootless():
436+
return (77, "SCHED_DEADLINE requires root")
437+
if not is_sched_deadline_available():
438+
return (77, "SCHED_DEADLINE not available in kernel")
439+
440+
conf = base_config()
441+
add_all_namespaces(conf)
442+
443+
# deadline > period (invalid)
444+
conf['process']['scheduler'] = {
445+
'policy': 'SCHED_DEADLINE',
446+
'runtime': 10000000, # 10ms
447+
'deadline': 30000000, # 30ms
448+
'period': 20000000 # 20ms
449+
}
450+
451+
conf['process']['args'] = ['/init', 'true']
452+
453+
try:
454+
out, _ = run_and_get_output(conf, hide_stderr=False)
455+
# Should have failed due to invalid order
456+
return -1
457+
458+
except subprocess.CalledProcessError as e:
459+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
460+
if "sched_setattr: `SCHED_DEADLINE` deadline" in output and "must be <=" in output and "period" in output:
461+
return 0 # Expected validation error
462+
logger.info("unexpected error: %s", output)
463+
return -1
464+
except Exception as e:
465+
logger.info("test failed: %s", e)
466+
return -1
467+
468+
469+
def test_scheduler_deadline_too_small_runtime():
470+
"""Test SCHED_DEADLINE validation - runtime < min."""
471+
if is_rootless():
472+
return (77, "SCHED_DEADLINE requires root")
473+
if not is_sched_deadline_available():
474+
return (77, "SCHED_DEADLINE not available in kernel")
475+
476+
conf = base_config()
477+
add_all_namespaces(conf)
478+
479+
conf['process']['scheduler'] = {
480+
'policy': 'SCHED_DEADLINE',
481+
'runtime': 1023, # too small
482+
'deadline': 10000000, # 10ms
483+
}
484+
485+
conf['process']['args'] = ['/init', 'true']
486+
487+
try:
488+
out, _ = run_and_get_output(conf, hide_stderr=False)
489+
# Should have failed due to too small runtime.
490+
return -1
491+
492+
except subprocess.CalledProcessError as e:
493+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
494+
if "sched_setattr: `SCHED_DEADLINE` runtime " in output and " must be between " in output:
495+
return 0 # Expected validation error
496+
logger.info("unexpected error: %s", output)
497+
return -1
498+
except Exception as e:
499+
logger.info("test failed: %s", e)
500+
return -1
501+
502+
503+
def test_scheduler_deadline_too_big_runtime():
504+
"""Test SCHED_DEADLINE validation - runtime > max."""
505+
if is_rootless():
506+
return (77, "SCHED_DEADLINE requires root")
507+
if not is_sched_deadline_available():
508+
return (77, "SCHED_DEADLINE not available in kernel")
509+
510+
conf = base_config()
511+
add_all_namespaces(conf)
512+
513+
conf['process']['scheduler'] = {
514+
'policy': 'SCHED_DEADLINE',
515+
'runtime': 9223372036854775809,
516+
'deadline': 9223372036854775810,
517+
}
518+
519+
conf['process']['args'] = ['/init', 'true']
520+
521+
try:
522+
out, _ = run_and_get_output(conf, hide_stderr=False)
523+
# Should have failed due to too big runtime.
524+
return -1
525+
526+
except subprocess.CalledProcessError as e:
527+
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
528+
if "sched_setattr: `SCHED_DEADLINE` runtime " in output and " must be between " in output:
529+
return 0 # Expected validation error
530+
logger.info("unexpected error: %s", output)
531+
return -1
260532
except Exception as e:
261533
logger.info("test failed: %s", e)
262534
return -1
@@ -270,7 +542,15 @@ def test_scheduler_flags():
270542
"scheduler-other": test_scheduler_other,
271543
"scheduler-nice-value": test_scheduler_nice_value,
272544
"scheduler-deadline": test_scheduler_deadline,
545+
"scheduler-deadline-no-period": test_scheduler_deadline_no_period,
273546
"scheduler-flags": test_scheduler_flags,
547+
"scheduler-deadline-missing-runtime": test_scheduler_deadline_missing_runtime,
548+
"scheduler-deadline-missing-deadline": test_scheduler_deadline_missing_deadline,
549+
"scheduler-deadline-zero-runtime": test_scheduler_deadline_zero_runtime,
550+
"scheduler-deadline-invalid-order": test_scheduler_deadline_invalid_order,
551+
"scheduler-deadline-invalid-deadline-period": test_scheduler_deadline_invalid_deadline_period,
552+
"scheduler-deadline-too-small-runtime": test_scheduler_deadline_too_small_runtime,
553+
"scheduler-deadline-too-big-runtime": test_scheduler_deadline_too_big_runtime,
274554
}
275555

276556
if __name__ == "__main__":

0 commit comments

Comments
 (0)