Part 1. One core · lesson 10 of 42
10. When it goes wrong¶
A command that does not finish is stopped by one of two time limits, and they are not the
same limit: the host's timeout ends only your waiting, and the card's deadline ends
the command. This lesson relies on lesson 8, Handlers and lesson 9, Many commands.
Why two limits¶
A program that waits for a core can wait for ever if the kernel has a bug. Komorebi has two answers, at two places.
- The timeout is in Python: the
timeout=ofwait. When it runs out,waitraiseskm.WaitTimeoutand the program decides what to do. The command is not touched. It may be slow, not broken, and you can callwaitagain. - The deadline is on the card: how long a command may take from
submituntil every core of its group has reported. The card measures it itself. When it runs out, the card stops the cores and the nextwaitraiseskm.CommandTimeout. The deadline is a setting ofload, and its default is one second.
The general form¶
run = session.load(program, deadline=SECONDS)
try:
event = run.wait(handle, timeout=SECONDS)
except km.WaitTimeout:
... # the host gave up waiting; the command is still on the card
except km.CommandTimeout as error:
... # the card gave up on the command; the run is over
deadline=is the card's limit for every command of this run.timeout=is the host's limit for this one wait.- After
WaitTimeoutthe run goes on: wait again, or send something else. - After
CommandTimeoutthe run has been stopped by the card, and so you do not callrun.stop(). To go on,loadagain.
A kernel that takes a while¶
To have something that is slow, the kernel of this lesson has a handler that counts up to the number it is given, which takes about six milliseconds per million:
writer.run([&](const km_command_t& command) {
if (km::handler(command) == kHandlerCount) {
const uint32_t limit = command.a0;
volatile uint32_t number = 0;
while (number < limit) {
number = number + 1;
}
writer.result(number);
}
return true;
});
volatile makes the compiler count for real instead of replacing the loop by its
result.
One limit at a time¶
handle = run.submit("count", group=(0, 1), args=[50_000_000], result=True)
try:
run.wait(handle, timeout=0.05)
except km.WaitTimeout:
print("the host stopped waiting after 0.05 seconds; the command goes on")
event = run.wait(handle, timeout=10.0)
print(f"a second wait: {event.kind}, the core counted to {event.result}")
The command counts to 50 million, about 0.3 seconds. The host waits 0.05 seconds, and
wait raises WaitTimeout. The except prints a line, and the command goes on: the
second wait waits up to ten seconds and gets the event.
handle = run.submit("count", group=(0, 1), args=[400_000_000], result=True)
try:
run.wait(handle, timeout=10.0)
except km.CommandTimeout as error:
print(f"the card stopped the command: {error}")
Now 400 million, more than two seconds, with a host that would wait ten. The card's
deadline of one second passes first. The card stops the cores, and wait raises
CommandTimeout, whose message is what the card reports.
The whole program¶
"""When it goes wrong: the host stops waiting, and the card stops a late command."""
from pathlib import Path
import komorebi as km
HERE = Path(__file__).resolve().parent
program = km.Program(
"when_it_goes_wrong",
kernels=HERE / "kernels",
handlers=HERE / "kernels" / "handlers.h",
)
with km.Session() as session:
run = session.load(program, deadline=1.0)
handle = run.submit("count", group=(0, 1), args=[50_000_000], result=True)
try:
run.wait(handle, timeout=0.05)
except km.WaitTimeout:
print("the host stopped waiting after 0.05 seconds; the command goes on")
event = run.wait(handle, timeout=10.0)
print(f"a second wait: {event.kind}, the core counted to {event.result}")
handle = run.submit("count", group=(0, 1), args=[400_000_000], result=True)
try:
run.wait(handle, timeout=10.0)
except km.CommandTimeout as error:
print(f"the card stopped the command: {error}")
// The writer kernel: it counts up to the first argument, which takes some time.
#include "komorebi/device/km_worker.hpp"
#include "handlers.h"
void kernel_main() {
km::Writer writer;
writer.run([&](const km_command_t& command) {
if (km::handler(command) == kHandlerCount) {
const uint32_t limit = command.a0;
volatile uint32_t number = 0;
while (number < limit) {
number = number + 1;
}
writer.result(number);
}
return true;
});
}
the host stopped waiting after 0.05 seconds; the command goes on
a second wait: done, the core counted to 50000000
the card stopped the command: command 2 timed out: slot 0, sequence 2, group first 0 count 1; not reported: core 0 reader gave up (stop) in next writer gave up (stop) in complete
Reading the last line, CommandTimeout's message:
command 2: the second command of this run, the one with handle 2.group first 0 count 1: the cores it was sent to.not reported: core 0: the core of the group that had not reported when the deadline passed.- what follows says where
reader.cppand writer of that core were when the card told them to stop. They are Komorebi's own step names, and you do not need them yet; lesson 39, Finding a bug uses them.
The card is fine afterwards. The counting kernel did end by itself, and a new program can open the card at once.
Common mistakes¶
- Reading
WaitTimeoutas "the command failed". It says that the host stopped waiting. - A
timeoutthat is longer than the deadline. The card's deadline passes first and the error isCommandTimeout, notWaitTimeout. - A deadline shorter than the honest running time of a command. The card then stops a command that was working. Set the deadline for the longest command of the program.
- A writer that does not return
true(lesson 4, Hello, core). The core never reports, and the card stops the command at its deadline withCommandTimeout.
That is the end of Part 1. You can now send work to a core, with numbers, get a number back, choose among several pieces of work, send many commands, and read what the card says when one does not finish. Part 2 does the same on many cores at once.
Next: lesson 11, A core group.