summaryrefslogtreecommitdiffstats
path: root/lib/command_queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/command_queue.c')
-rw-r--r--lib/command_queue.c186
1 files changed, 110 insertions, 76 deletions
diff --git a/lib/command_queue.c b/lib/command_queue.c
index 7946d0e8..b80b2c9f 100644
--- a/lib/command_queue.c
+++ b/lib/command_queue.c
@@ -33,78 +33,77 @@
/*==============================================================================
* This command loop processes commands for VTY_TERMINAL and VTY_SHELL_SERVER.
*
- * Commands appear from those sources, driven by socket I/O. Once a command
- * line is ready it is passed out of the I/O pselect/select driven code
- * as a message, and enters the command loop.
+ * The command loop is a form of co-routine, which is "called" by sending a
+ * message (or by legacy threads events). In the multi-pthread world, the
+ * command loop may run in either the vty_cli_nexus or the vty_cmd_nexus, and
+ * may be passed from one to the other by sending a message.
*
- * If pipes are involved that is all dealt with in the command loop, which
- * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and
- * VTY_SHELL_SERVER.
+ * The command loop is usually in one of three states:
*
- * There are further issues: fix to current state TODO
+ * vc_running -- the command loop is running, doing things.
*
- * 1) in the qpthreads world, commands are parsed in the CLI thread, but most
- * are executed in the Routing thread. So parsed commands are passed, by
- * message between threads.
+ * In this state the vty and the vty->exec are in the hands
+ * of the command loop.
*
- * 2) input is expected to be non-blocking -- so the command loop will
- * exit if a command line cannot be delivered immediately, and will be
- * returned to later.
+ * The vty->vio is always accessed under VTY_LOCK().
*
- * 3) while a VTY is in the command loop it is marked vio->cmd_running. TODO
+ * Note that one of the things the command loop may be
+ * doing is waiting for a message to be picked up (or
+ * a legacy thread event to be dispatched).
*
- * While that is true, the vty and the vty->exec are in the hands
- * of the command loop. The vty->vio is always accessed under VTY_LOCK().
+ * vc_waiting -- the command loop is waiting for something to happen
+ * (typically some I/O), and will stay there until a
+ * message is sent (or a legacy threads event).
*
- * 4) opening pipes is done in the CLI thread, in case of any possible
- * blocking.
+ * To be waiting the command loop must have entered "hiatus"
+ * and then exited.
*
- * 5) all output is to fifo buffers -- when output is pushed the CLI side
- * is kicked to manage all output via pselect/select.
+ * When a command loop is or has been stopped, to goes to vc_stopped state.
*
- * In vty_io_basic() it is possible to set read/write ready and associated
- * timeouts while running in the CMD thread, but that too depends on the
- * passing of messages to the CLI thread.
+ * There are further issues:
*
- * The smooth running of the command handling depends on the continued
- * running of the CLI thread.
+ * * In the mult-pthreaded world, commands may be parsed in either pthread,
+ * but most are executed in the Routing thread. So may switch pthreads
+ * after parsing a command -- by sending a message.
*
- * To close a VTY must (eventually) arrange for vio->cmd_running to be cleared.
- * While a vty is vio->cmd_running, it must be in one of these states: TODO
+ * * opening pipes is done in the vty_cli_nexus, in case of any possible
+ * blocking and because the pselect() stuff is all done in the
+ * vty_cli_nexus.
*
- * - on the vty_cli_nexus queue (or the combined queue)
- * - executing in the vty_cli_nexus (or the single "both" nexus)
- * - on the vty_cmd_nexus queue (if different)
- * - executing in the vty_cmd_nexus (if different)
- *
- * Or, in the legacy threads world:
+ * * all output is via fifo buffers -- when output is pushed, as much as
+ * possible is done immediately, but if necessary the pselect() process
+ * (in the vty_cli_nexus) is left to keep things moving.
*
- * - on the event queue
- * - executing
+ * In vty_io_basic() it is possible to set read/write ready and associated
+ * timeouts while running in the vty_cmd_nexus, but note that this is done
+ * by sending messages to the CLI thread (if multi-pthreaded).
*
- * Where there is only one pthread (and in the legacy threads world) things are
- * easy.
+ * The smooth running of the command handling depends on the continued
+ * running of the CLI thread.
*
+ * To close a VTY must (eventually) arrange for the state to not be vc_running.
+ * While a vty is vc_running, the command loop may be in either nexus, and
+ * may be:
*
- * This revoke runs in the CLI thread, and will catch the message if it is
- * on either queue, and vty_revoke() will deal with it -- still in the CLI
- * thread.
+ * - on the vty_cli_nexus queue (or the combined queue)
+ * - executing cq_process() in the vty_cli_nexus (or the single "both" nexus)
+ * - on the vty_cmd_nexus queue (if different)
+ * - executing cq_process() in the vty_cmd_nexus (if different)
*
- * The command cannot be running in the CLI thread, since that is where we
- * are !
+ * where being on the queue means that there is a message waiting to be
+ * picked up on that queue, which will end up calling cq_process().
*
- * That leaves the command running in the CMD thread. That will run to
- * completion... the VTY may be closed in the meantime, which will shut down
- * the reading side, so the command loop will come to a halt quite quickly.
- * Note, however, that the smooth running of this process requires the CLI
- * thread and its messages to be
+ * Or, in the legacy threads world:
*
+ * - on the event queue -- waiting for event handler to call cq_process()
+ * - executing cq_process()
*
+ * To bring the command loop to a stop will call cq_revoke to revoke any
+ * pending message or pending legacy event. If that succeeds, then the
+ * command loop can be stopped immediately. If not, then vc_running implies
+ * it is executing in cq_process() in one of the nexuses.
*/
-
-
-
/*------------------------------------------------------------------------------
* Prototypes
*/
@@ -127,7 +126,7 @@ cq_loop_enter(vty vty, cmd_return_code_t ret)
{
VTY_ASSERT_CLI_THREAD() ;
- assert(vty->exec->state == exec_null) ;
+ qassert(vty->exec->state == exec_null) ;
cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ;
} ;
@@ -140,7 +139,7 @@ cq_continue(vty vty, cmd_return_code_t ret)
{
VTY_ASSERT_CLI_THREAD() ;
- assert(vty->exec->state == exec_hiatus) ;
+ qassert(vty->exec->state == exec_hiatus) ;
cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
} ;
@@ -150,6 +149,8 @@ cq_continue(vty vty, cmd_return_code_t ret)
*
* Will execute in the current exec->state, passing in the given return
* code.
+ *
+ * Note that the vio->state *must* be vc_running.
*/
static void
cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
@@ -157,7 +158,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
{
cmd_exec exec = vty->exec ;
- assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ;
+ qassert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ;
exec->locus = dst ;
exec->state = state ;
@@ -175,7 +176,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
}
else
{
- assert(vty_cli_nexus == vty_cmd_nexus) ;
+ qassert(vty_cli_nexus == vty_cmd_nexus) ;
exec->cq.thread = thread_add_event(vty_master, cq_thread, vty, 0) ;
} ;
} ;
@@ -183,7 +184,11 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
/*------------------------------------------------------------------------------
* Deal with command message -- in the qpthreads world.
*
- * Note that if the command is revoked.... TODO
+ * Note that if the message is revoked, then the state is set vc_stopped.
+ * We revoke when stopping a command loop -- see cq_revoke, below. If the
+ * message is revoked at any other time then the command loop is, indeed,
+ * stopped -- the I/O side may plow on until buffers empty, but the
+ * command loop will not cq_continue(). At
*/
static void
cq_action(mqueue_block mqb, mqb_flag_t flag)
@@ -194,33 +199,35 @@ cq_action(mqueue_block mqb, mqb_flag_t flag)
vty = mqb_get_arg0(mqb);
- assert(vty->exec->cq.mqb == mqb) ;
+ qassert(vty->exec->cq.mqb == mqb) ;
if (flag == mqb_action)
- return cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
/* do not touch vty on way out */
+ else
+ {
+ /* Revoke action. */
+ mqb_free(mqb) ;
+ vty->exec->cq.mqb = NULL ;
- /* Revoke action. */
- mqb_free(mqb) ;
- vty->exec->cq.mqb = NULL ;
+ vty_cmd_set_stopped(vty) ; /* enforced stop of loop */
+ } ;
} ;
/*------------------------------------------------------------------------------
* Deal with command message -- in the legacy threads world.
- *
- * Note that if the command is revoked.... TODO
*/
static int
cq_thread(struct thread* thread)
{
vty vty = THREAD_ARG(thread) ;
- assert(vty->exec->cq.thread == thread) ;
+ qassert(vty->exec->cq.thread == thread) ;
vty->exec->cq.thread = NULL ;
cq_process(vty, vty->exec->state, vty->exec->ret) ;
- /* do not touch vty on way out */
+ /* do not touch vty on way out */
return 0 ;
} ;
@@ -248,7 +255,7 @@ cq_thread(struct thread* thread)
*
* CMD_SUCCESS => can proceed to fetch the next command
* CMD_WAITING => must leave the command loop, waiting for I/O
- * CMD_EOF => close the vty and leave command loop
+ * CMD_STOP => close the vty and leave command loop
* CMD_IO_ERROR => loop back and deal with the error
*
* The cq_loop_enter() and cq_continue() operations send a message (to the
@@ -261,7 +268,11 @@ cq_thread(struct thread* thread)
* life simple, switches back to the cli thread if ends up waiting for I/O.)
*
* The vty_io_basic stuff allows the multi-pthread vty_cmd_nexus to set read
- * and/or write ready state -- which may generate messages to the vty_cli_nexus.
+ * and/or write ready state -- which may generate messages to the
+ * vty_cli_nexus.
+ *
+ * NB: on exit from cq_process the VTY may have been closed down and released,
+ * so do NOT depend on its existence.
*/
static void
cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
@@ -341,7 +352,7 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
ret = vty_cmd_reflect_line(vty) ;
if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
- break ;
+ break ; /* CMD_IO_ERROR or CMD_HIATUS */
} ;
fall_through ;
@@ -447,20 +458,28 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
/*--------------------------------------------------------------------
* Hiatus state -- some return code to be dealt with !
+ *
+ * If we are not in the vty_cli_nexus, then must pass the problem
+ * to the vty_cli_nexus. If the return code is CMD_STOP, or there
+ * is a CMD_STOP signal, then drop the config symbol of power -- see
+ * uty_close().
*/
case exec_hiatus:
if (exec->locus != vty_cli_nexus)
- return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+ {
+ vty_cmd_check_stop(vty, ret) ;
+ return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+ } ;
while (1)
{
- /* Let vty_cmd_hiatus() deal with the return code, and adjust
- * the stack as required.
+ /* Let vty_cmd_hiatus() deal with the return code and/or any
+ * stop/error/etc trap, and adjust the stack as required.
*/
ret = vty_cmd_hiatus(vty, ret) ;
if (ret == CMD_SUCCESS)
- break ;
+ break ; /* back to exec_fetch */
if (ret == CMD_WAITING)
{
@@ -468,7 +487,10 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
return ; /* <<< DONE, pro tem */
} ;
- if (ret == CMD_EOF)
+ if (ret == CMD_IO_ERROR)
+ continue ; /* give error back to hiatus */
+
+ if (ret == CMD_STOP)
{
exec->state = exec_stopped ;
vty_cmd_loop_exit(vty) ;
@@ -476,9 +498,6 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
return ; /* <<< DONE, permanently */
} ;
- if (ret == CMD_IO_ERROR)
- continue ;
-
zabort("invalid return from vty_cmd_hiatus()") ;
} ;
@@ -524,7 +543,13 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
* Note that the revoke does not affect any vty_cli_nexus messages associated
* with the vty_io_basic operations.
*
+ * If succeeds in revoking, then will have set the state to vc_stopped, and
+ * will have released the message block or legacy thread object.
+ *
* Returns: true <=> have revoked a pending message/event
+ *
+ * NB: if no command loop has ever been started for this vty, then will not
+ * find anything to revoke.
*/
extern bool
cq_revoke(vty vty)
@@ -541,7 +566,16 @@ cq_revoke(vty vty)
ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ;
}
else
- ret = thread_cancel_event(vty_master, vty) ;
+ {
+ ret = thread_cancel_event(vty_master, vty) ;
+
+ if (ret != 0)
+ {
+ /* Make sure in same state as after mqueue_revoke. */
+ vty_cmd_set_stopped(vty) ;
+ vty->exec->cq.thread = NULL ;
+ } ;
+ } ;
/* If revoked message/event, the command loop is stopped. */
if (ret != 0)