Table of Contents

Scheduler

This document covers the technical implementation of the scheduler. It is intended for anyone who needs to replace, modify, or debug the scheduler.

This document is not intended for people who want to create an agent (although it certainly helps to know this information).

About the Scheduler

The scheduler is a super-agent, responsible for spawning and managing all other agents. The scheduler balances the needed tasks (found in the job queue) with the available resources. It tries to ensure that:

  1. One task does not lock out other tasks.
  2. Unused resources are used by other pending tasks.
  3. Agents are spawned in an optimal fashion (fastest).
  4. Agents do not exceed the alloted resources.

Although the scheduler is single-threaded, it manages child processes that run in parallel.

Front-End Communications

The scheduler communicates with the front-end UI through the database’s jobqueue table. This table lists which agents need to run, the necessary parameters for the agent, and the current operation status.

The combination of host and query type leads to four combinations, but only two combinations are implemented.

Any host Host-specific
One parameter OK N/A
MSQ N/A OK via runonpfile

Some example agents:

Some agents could fit into other categories, but are limited by the two available choices:

Adding to the Queue

Jobs are added to the jobqueue by the front-end UI. The UI knows the desired agent type, whether it is an any-host or MSQ job, and the proper parameters (or SQL). The scheduler has no control over what is added and does not validate whether the added job is correct.

Job Tracking

The scheduler tracks jobs based on the jobqueue start and end times.

Some jobs need to be rescheduled. For example, an MSQ may have a “LIMIT 5000” (allowing the scheduler to only manage a few results at a time and permitting a limited timeslice scheduling). This is done by removing the start time when the job completes – effectively putting the job back into the jobqueue. If the MSQ returns no results, then the end-time is set, completing the job.

The jobs run with the following priorities:

  1. Anything currently running is allowed to run. The rationale: a job may take a long time and it is better not to cancel the job and try to restart it later.
  2. Any jobs held by the scheduler come next.
    1. Of the jobs held by the scheduler, jobs for available, active, agents come first. This reduces kill/spawn times.
    2. If there are no running agents of the correct type, then one is spawned (possibly after killing an incorrect ready/active agent type first).
  3. The job queue has a column for urgent tasks. These come next.
  4. Any available job, oldest first.
  5. Jobs that do not match the available agents are ignored and remain in the jobqueue.

The jobqueue keeps a prioritized table in case one agent depends on the results from another agent. As a result, there are frequently jobs in the jobqueue that cannot run (temporarily blocked due to a dependency).

This tracking method has a few limitations:

The main function for checking the queue is in dbq.c: DBProcessQueue(). This checks the jobqueue for new tasks and processes the held MSQ records.

Signals

The scheduler watches for a few signals. These are mainly used for debugging:

Back-end Children

All children are treated as finite-state machines. The states (defined in spawn.h) are:

Most of the time, the children are in the ST_FREE, ST_READY, or ST_RUNNING states.

NOTE: When the scheduler runs, it displays the different state transitions for each child. However, not all transitions are shown. Since active children switch rapidly between ST_READY and ST_RUNNING, these transitions (to and from) are not displayed.

Talking with Children

Each spawned agent has stdin and stdout redirected to the scheduler. (Stderr is not redirected.) The workflow is as follows:

  1. The agent is spawned by the scheduler. (Scheduler creates child.) This happens in the GetChild() function. The state is changed from ST_DEAD to ST_PREP (for populating the data structure) to ST_SPAWNED (indicating a process fork).
  2. When the child is initialized and ready, it writes “OK\n” to stdout. This tells the scheduler that the child can accept data (ST_SPAWNED becomes ST_READY).
  3. The child begins reading from stdin.
    1. If stdin closes, then the child should die as quickly as possible.
    2. If data appears on stdin, then the child should process it. The data will either come from the jq_args column (for any-host agents), or be the results from the MSQ query (in ‘column=value’ pairs, all on one line).
  4. When the scheduler sends data to the child (via stdin), the state is changed from ST_READY to ST_RUNNING. No further data will be sent until the child is ready.
  5. When the child finishes the task AND is ready for the next task, it writes “OK\n” to stdout. This transitions the child from ST_RUNNING to ST_READY. Note: The scheduler sees is no distinction between a child completing the first task and getting ready for the next task.

There are a few additional messages that the child can send to stdout:

Due to the “DB:” limitations, agents should communicate directly with the DB rather than use the “DB:” command. (The “DB:” was created as a test and has some uses, but agents should not depend on it.)

Killing Children

The scheduler limits the number of spawned processes by host. Thus, if a host has a maximum of 4 spawned processes and a fifth is needed, then an existing child is killed first.

Under normal circumstances, only children in the ST_READY state are killed. Killing occurs as follows:

  1. Stdin to the child is closed AND the child is sent a SIGHUP. The child is moved from ST_READY to ST_FREEING.
  2. The child may choose to catch SIGHUP and cleanup any remaining tasks.
  3. If the child sees that stdin is closed, then it must exist ASAP. Similarly, if the child catches SIGHUP then it must exit quickly.
  4. Since the child was at ST_READY, the jobqueue is not modified.
  5. If the child is still in ST_FREEING after 20 seconds (defined in spawn.h as MINKILLTIME), then the child is assassinated using SIGKILL.
  6. When the child dies, a SIGCHLD is sent to the scheduler. This is caught and used to transition the child from ST_FREEING to ST_FREE.

There are some abnormal circumstances when the child may die...

Other situations when a child may be ordered to die (normal death) or kept alive too long:

Configuring the Scheduler

The scheduler uses a configuration file to specify the number of processes per host and each agent. A configuration file creator script, mkconfig, is available to aid in the creation of this file. For example:

%Host localhost 2 1
agent=wget host=localhost | /usr/local/fossology/agents/wget_agent
agent=unpack host=localhost | /usr/local/fossology/agents/engine-shell unpack '/usr/local/fossology/agents/ununpack -d /home/repository//ununpack/%{U} -qRCQx'
agent=filter_license host=localhost | /usr/local/fossology/agents/Filter_License
agent=filter_license host=localhost | /usr/local/fossology/agents/Filter_License
agent=license host=localhost | /usr/local/fossology/agents/bsam-engine -L 20 -A 0 -B 60 -G 10 -M 2 -E -T license -O n -- - /usr/local/share/fossology/agents/License.bsam
agent=mimetype host=localhost | /usr/local/fossology/agents/mimetype
agent=mimetype host=localhost | /usr/local/fossology/agents/mimetype
agent=specagent host=localhost | /usr/local/fossology/agents/specagent
agent=filter_clean host=localhost | /usr/local/fossology/agents/filter_clean -s
agent=pkgmetagetta host=localhost | /usr/local/fossology/agents/pkgmetagetta
agent=pkgmetagetta host=localhost | /usr/local/fossology/agents/pkgmetagetta

The format of the file is as follows:

Running the Scheduler

Usage: ./scheduler [options] [setup.conf] < 'type command'
  -d :: Run as a daemon!  Still generates stdout and stderr
  -H :: Ignore hosts for host-specific agent requests
  -I :: Use stdin and queue (default: use queue only)
  -v :: verbose (-v -v = more verbose)
  -L log :: send stdout and stderr to log
  -q :: turn off show stages
  -R :: reset the job queue in case something was hung
  setup.conf: defines each engine -- one 'type command' per line.
  If setup.conf is not specified, then /usr/local/share/fossology/agents/scheduler.conf is used.
  stdin lists type+data, one per line.
  stdout comes from threads, non-interlaced and only when thread ends.
  stderr comes from threads, interlaced and immediate.
Each command is executed as a running engine.
Each stdin line is matched to a free engine of the same type.
If no engine is free, then it will pause until one is available.

The agent usually logs all output to a log file and processes data from the queue. It also runs as a daemon so you can logout without killing the scheduler.

./scheduler -d -L log setup.conf

For testing:

If the scheduler is killed using “kill -9”, then the queue may not be reset to a stable condition. When you start the scheduler, it will monitor the queue. After 10 minutes of inactivity, the abandoned queue entries will be reclaimed for use by the scheduler. For a faster response, you can use “-R” to reset the queue immediately. However, don’t use -R if there are multiple schedulers running at the same time. (Multiple schedulers is not supported, but -R will make a bad situation worse.)

Commanding the Scheduler

The scheduler runs as an independent back-end process from the front-end user interface. As a result, the UI cannot communicate directly with the scheduler. Instead, all commands are placed in the database’s jobqueue. During normal operations, the jobqueue stores tasks to be run (the tasks should match the scheduler’s configuration file). However, there is one special jobqueue task.

jq_type = "command"
jq_args = parameters for command

When the jobqueue’s jq_type is the lowercase string “command”, the parameters in “jq_args” are interpreted directly by the scheduler. The following jq_args are supported.

The front-end UI knows that the job is complete because it will be marked as processed in the jobqueue.

Building the Scheduler

The scheduler consists of 5 source files:

To build the scheduler, use the Makefile.

make clean        # remove all compiled files (clean slate for a new build)
make              # build the scheduler
sudo make install # install it to /usr/local/fossology/agents/

The make command should build without any errors or warnings (a clean make).

NOTE: If you make any changes to the state machine labels (the ST_* definitions in spawn.h) then you must use ‘make clean’ before ‘make’. (Someday we might introduce a ‘make depends’ file so code is compiled when all dependencies change.)