-
Notifications
You must be signed in to change notification settings - Fork 2
Executor
This executor
's jobs is to understand the student API and execute the student code using the corresponding Runtime functions. This process is probably the most delicate as it has to accommodate any type of student code.
This process will read the current mode
from the shm_wrapper_aux
which will be supplied by the net_handler
. Then whenever the mode
changes to AUTO
or TELEOP
, the main
function will create a new subprocess to run the new mode, using fork
and run_mode
. If mode
changes to IDLE
, it will kill the previously running process using kill_subprocess
. To avoid any robot safety concerns, we must also reset the robot parameters on IDLE
, which is done with reset_params
. The main
function handling the mode changes will loop forever and is only cancellable by sending a SIGINT signal.
To actually have the student code call the student API functions, we need to insert the API functions into the student module's namespace. This is done in executor_init
where the Python interpreter is initialized. The insertion is done by setting the student code's attributes Robot
and Gamepad
to the corresponding attributes in the student API.
Before the mode actually begins, we need to first send device subscription requests to the dev_handler
to ensure that SHM is being updated with device data. To do this, we must parse the student code to see which devices and parameters they are using, then send the corresponding requests. These functions are in code_parser.pyx
which is included in the studentapi.pyx
namespace using the Cython include
statement. Then in executor_init
, we call make_device_subs
from code_parser.pyx
.
run_mode
will call the <mode>_setup
and the <mode>_main
Python functions using run_py_function
. These functions will end up calling the Python C API located in "Python.h"
to run the Python functions in the given student code, which is assumed to be in studentcode.py
. More can be read about the Python C API at https://python.readthedocs.io/en/stable/c-api/index.html.
The last mode is CHALLENGE
which is used to run the student's coding challenges. The subprocess is started the same as the other modes but will call run_challenges
instead of run_mode
. This function will start reading from the challenge UNIX socket that is listening to the net_handler
for the challenge inputs. It then runs the challenges with its inputs using run_py_function
. Finally, it will return the outputs as a string to the net_handler
by sending over the challenge UNIX socket.
The tricky thing for the CHALLENGE
mode is that it isn't idempotent like the other 2 modes since it requires an input. As a result, we need to ensure that if the CHALLENGE
mode finishes or gets cancelled, it will set the mode to IDLE
on exit.
One thing to note which can cause unwanted interactions is that the executor process is now linked dynamically, and so it shares the symbols for the shared memory and logger with the studentapi.pyx
file. This means that if you initialize it once on executor.c
, you don't need to initialize it in studentapi.pyx
.
Another important caveat to be aware of is Python's global interpreter lock (GIL). The Python interpreter can run in only 1 thread at a time and which thread is running is the one that acquires the GIL. As a result, whenever any Python C API functions need to be called, you must always be careful to acquire the GIL first and then release it when you are done. To make it simpler, we designed the architecture such that each mode runs in a separate process instead of a separate thread, which avoids any issue with the Python interpreter state.
More details on how all these functions work are in the function documentation in executor.c
.
- Important
- Advanced/Specific