diff options
Diffstat (limited to 'pkg/ecl/prcache.c')
-rw-r--r-- | pkg/ecl/prcache.c | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/pkg/ecl/prcache.c b/pkg/ecl/prcache.c new file mode 100644 index 00000000..cf6db5a1 --- /dev/null +++ b/pkg/ecl/prcache.c @@ -0,0 +1,708 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#define import_spp +#define import_libc +#define import_stdio +#define import_error +#define import_finfo +#define import_prstat +#include <iraf.h> + +#include "config.h" +#include "errs.h" +#include "task.h" +#include "operand.h" +#include "proto.h" + + +/* + * PRCACHE -- To minimize spawns, we maintain a cache of processes. Each + * process may contain any number of tasks. Zero or one tasks may be active + * in a process at a given time. A process is spawned and added to the + * cache when a task therein needs to be run. A process is terminated when + * its cache slot is needed by another process or when the cache is flushed. + * Error recovery does not normally result in process termination, even when + * the error is initiated by a task resident in the process. + * + * pid = pr_connect (process, command, &in,&out, tin,tout,terr, timeit) + * pr_disconnect (pid) + * pr_lock (pid) + * pr_unlock (pid) + * pr_dumpcache (pid, break_locks) + * pr_chdir (pid, newdir) + * pr_envset (pid, envvar, valuestr) + * pid = pr_cachetask (ltname) + * pid = pr_pnametopid (pname) + * pr_listcache (fp) + * pr_setcache (sz_prcache) + * pno = pr_getpno () + * pr_prunecache (pno) + * + * The PR_CONNECT procedure executes an ltask resident in an external compiled + * process. A process spawn occurs only if the process is not found in + * the cache or is not idle. PR_DISCONNECT should be called when the ltask + * terminates to signal that the process is idle. Processes may be locked + * in the cache, but this facility must be used with great discretion as + * it defeats the purpose of the cache and may lead to lockout. + * + * A process is passed the environment list and the name of the current working + * directory when it is spawned. New SET environment declarations or chdir + * directives may be passed to all processes in the cache without flushing + * and refilling the cache, using the PR_CHDIR and PR_ENVSET commands. + * Pseudofile i/o (xmit and xfer) is handled automatically by the system. + * Our function here is to connect the pseudofile streams of the ltask + * up to real streams at connect() time, via calls to c_prredir(). + * + * The size of the cache is a runtime time parameter controlled by the CL + * parameter `szprcache'. The default value of this is set either in + * cl$cl.par or in hlib$clpackage.par, hence may vary from site to site + * or even from host to host. + */ + +extern int cldebug; +extern int cltrace; + +typedef XINT (*PFI)(); + +struct process { + int pr_pid; /* process id of subprocess */ + long pr_time; /* time when process executed */ + short pr_flags; /* flag bits */ + short pr_pno; /* prcache process number */ + FILE *pr_in, *pr_out; /* in, out IPC channels */ + struct process *pr_up; /* up link (toward head) */ + struct process *pr_dn; /* down link (toward tail) */ + char pr_name[SZ_PATHNAME+1]; /* filename of process */ +}; + +#define P_ACTIVE 01 /* task in process is in use */ +#define P_LOCKED 02 /* process is locked in cache */ + +#define pr_idle(pr) (((pr)->pr_flags&P_ACTIVE)==0) +#define pr_busy(pr) (((pr)->pr_flags&(P_ACTIVE|P_LOCKED))!=0) + +int pr_pno = 1; /* incremented for each connect */ +int sz_prcache = 2; /* nprocess slots in cache */ +struct process pr_cache[MAXSUBPROC]; +struct process *pr_head = NULL, *pr_tail = NULL; +extern char *findexe(); + +static void pr_pdisconnect (struct process *pr); +static void pr_tohead (struct process *pr); +static void pr_totail (struct process *pr); +static void pr_unlink (struct process *pr); + + + +/* PR_CONNECT -- Run a task resident in an external process. Look in the cache + * for the named process; if not found or already active, spawn the process + * and add it to the cache. Send the startup message to the child to start + * the task in execution. The startup message specifies the name of the task + * to be run, whether timing is desired, and any i/o redirection desired. + * The input and output IPC file pointers are returned to the caller. + * + * TODO: This procedure was designed to minimize the changes to the high level + * code, and is not done right. Formatting of the startup command should be + * done in a procedure within this package, rather than at the high level, + * and should support i/o redirection to named files for (greatly) increased + * efficiency of pipes. + */ +int +pr_connect ( + char *process, /* filename of process */ + char *command, /* IRAF Main command */ + FILE **in, + FILE **out, /* IPC channels (output) */ + FILE *t_in, + FILE *t_out, + FILE *t_err, /* task stdin,out,err (input) */ + FILE *t_gr, + FILE *t_im, + FILE *t_pl, /* task graphics streams */ + int timeit /* if !0, time command */ +) +{ + register int pid; + + /* Connect subprocess. */ + if ((pid = pr_pconnect (process, in, out)) == NULL) + c_erract (EA_ERROR); + + + /* Set default redirection of the standard i/o streams. + */ + c_prredir (pid, STDIN, fileno(t_in)); + c_prredir (pid, STDOUT, fileno(t_out)); + c_prredir (pid, STDERR, fileno(t_err)); + c_prredir (pid, STDGRAPH, fileno(t_gr)); + c_prredir (pid, STDIMAGE, fileno(t_im)); + c_prredir (pid, STDPLOT, fileno(t_pl)); + + /* Send startup message. */ + if (timeit) + fputc ('$', *out); + fputs (command, *out); + fflush (*out); + + if (cldebug) + eprintf ("connect: *in, *out, t_in, t_out: %d %d %d %d\n", + *in, *out, t_in, t_out); + if (cltrace) { + d_fmtmsg (stderr, "\t ", command, 80 - 13); + eprintf ("\t--------------------------------\n"); + } + + return (pid); +} + + +/* PR_DISCONNECT -- Called when a task resident in an external process + * terminates; also called during error recovery, e.g., following X_IPC. + * Our only function for normal task termination is to clear the active flag. + * Until the active flag is cleared the process cannot be reused nor terminated. + */ +void +pr_disconnect ( + int pid /* process id returned by connect */ +) +{ + struct process *pr; + + pr_checkup(); + for (pr=pr_head; pr != NULL; pr = pr->pr_dn) { + if (pr->pr_pid == pid) { + pr->pr_flags &= ~P_ACTIVE; + return; + } + } +} + + +/* PR_PCONNECT -- Run a task resident in an external process. Look in the cache + * for the named process; if not found or already active, spawn the process + * and add it to the cache. Return the process id and file pointers to the + * IPC channels to the caller. + */ +int +pr_pconnect ( + char *process, /* filename of process */ + FILE **in, + FILE **out /* IPC channels (output) */ +) +{ + struct process *pr; + struct process *pr_findproc(); + struct _finfo fi; + int fd_in, fd_out; + extern int c_finfo (char *name, struct _finfo *fi); + + if (pr_head == NULL) + pr_initcache(); + else + pr_checkup(); + + /* Search the cache to see if the process is already connected and + * inactive. If the process is found idling in the cache, relink it + * at the head of the cache list, otherwise disconnect the inactive + * process nearest the tail of the list and spawn the new one to + * replace it. The cached entry is automatically invalidated if the + * corresponding executable file has been modified (e.g., relinked), + * provided the process is not currently busy. A process is considered + * busy if it is active or if it is locked in the cache. + */ + fi.fi_mtime = 0; + if ((pr = pr_findproc (process)) != NULL && !pr_busy(pr)) { + if (c_finfo (process, &fi) == ERR || fi.fi_mtime > pr->pr_time) { + pr_pdisconnect (pr); + pr = NULL; + } + } + + if (pr != NULL) + pr_tohead (pr); + else { + /* Get process slot. */ + for (pr=pr_tail; pr != NULL; pr=pr->pr_up) + if (!pr_busy(pr)) { + if (pr->pr_pid != NULL) + pr_pdisconnect (pr); + break; + } + if (pr == NULL) + cl_error (E_UERR, "process cache deadlock"); + pr_tohead (pr); + + /* Spawn subprocess. Turn off interrupts during process startup + * to avoid crashing the IPC protocol. + */ + if (cltrace) + eprintf ("\t----- connect to %s -----\n", process); + intr_disable(); + if ((pr->pr_pid = c_propen (process, &fd_in, &fd_out)) == NULL) { + intr_enable(); + return (NULL); + } + intr_enable(); + + if (fi.fi_mtime == 0) + if (c_finfo (process, &fi) == ERR) + fi.fi_mtime = 0; + + pr->pr_time = fi.fi_mtime; + pr->pr_in = FDTOFP (fd_in); + pr->pr_out = FDTOFP (fd_out); + pr->pr_flags = 0; + pr->pr_pno = pr_getpno(); + strcpy (pr->pr_name, process); + } + + pr->pr_flags |= P_ACTIVE; + *in = pr->pr_in; + *out = pr->pr_out; + + return (pr->pr_pid); +} + + +/* PR_PDISCONNECT -- Remove a process from the process cache. Processes are + * disconnected when pushed out of the cache or when the cache is flushed. + */ +void +pr_pdisconnect (struct process *pr) +{ + /* Ignore attempts to dump active processes. This might happen + * when an active process executes a command which calls dumpcache. + */ + if (pr == NULL || pr->pr_pid == NULL || pr_busy(pr)) + return; + + if (cltrace) + eprintf ("\t----- disconnect %s -----\n", pr->pr_name); + + /* Command child process to exit, close down communications. This + * closes the IPC files as well as the terminating the process. + */ + c_prclose (pr->pr_pid); + + /* Clear process table entry and move process to tail of list. + */ + pr->pr_pid = 0; + pr_totail (pr); +} + + +/* PR_SETCACHE -- Set the size of the process cache. This is automatically + * called whenever the value of the parameter cl.szprcache is set. Changing + * the cache size on an active cache causes the cache to be flushed and all + * locked processes to be reconnected. + */ +void +pr_setcache (int new_szprcache) +{ + struct process *pr; + char pname[MAXSUBPROC][SZ_PATHNAME+1]; + int nprocs=0, pid, i; + FILE *fdummy; + + if (pr_head == NULL) + pr_initcache(); + else { + /* Get the names of any processes currently locked into the cache, + * then dump the cache. + */ + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) + if (pr->pr_pid != NULL && (pr->pr_flags & P_LOCKED)) + strcpy (pname[nprocs++], pr->pr_name); + pr_dumpcache (0, 1); + } + + /* Set the new value of sz_prcache. */ + sz_prcache = new_szprcache; + if (sz_prcache < 2) + sz_prcache = 2; + else if (sz_prcache > MAXSUBPROC) + sz_prcache = MAXSUBPROC; + + /* Relink the empty cache for sz_prcache cache slots. */ + pr_initcache(); + + /* Attempt to recache the formerly locked processes. There must be + * at least one empty slot left for new subprocesses. + */ + if (nprocs+1 > sz_prcache) + nprocs = sz_prcache-1; + + for (i=0; i < nprocs; i++) { + pid = pr_connect (findexe(NULL,pname[i]), "\n", &fdummy, &fdummy, + stdin, stdout, stderr, 0,0,0, 0); + pr_disconnect (pid); + pr_lock (pid); + } +} + + +/* PR_FINDPROC -- Search the cache for the named process. Skip active + * processes. + */ +struct process * +pr_findproc (char *process) +{ + struct process *pr; + + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) + if (pr->pr_pid != NULL && pr_idle(pr)) + if (strcmp (process, pr->pr_name) == 0) + return (pr); + + return (NULL); +} + + +/* PR_CACHETASK -- Cache the process containing the named logical task. + * If the process is already connected merely returns its pid, else connect + * the process and return its pid. + */ +int +pr_cachetask ( + char *ltname /* logical task name */ +) +{ + register int pid; + struct ltask *ltp; + FILE *fdummy; + + ltp = ltasksrch ("", ltname); + if (ltp->lt_flags & (LT_SCRIPT|LT_BUILTIN)) + return (ERR); + if ((pid = pr_pnametopid(findexe(ltp->lt_pkp,ltp->lt_pname))) == NULL) { + pid = pr_connect (findexe(ltp->lt_pkp,ltp->lt_pname), "\n", &fdummy, + &fdummy, stdin, stdout, stderr, 0,0,0, 0); + pr_disconnect (pid); + } + + return (pid); +} + + +/* PR_LOCK -- Lock a connect process in the cache. Must be used with caution + * as deadlock may occur. Locked processes are also not disconnected by + * pr_dumpcache, which may not be what is desired. + */ +void +pr_lock ( + register int pid /* process id */ +) +{ + struct process *pr; + + if (pid != NULL) + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) + if (pr->pr_pid == pid) { + pr->pr_flags |= P_LOCKED; + break; + } +} + + +/* PR_UNLOCK -- Unlock a process, allowing it to be disconnected either when + * forced out of the cache by another disconnect, or by a dumpcache. + * + * This function is currently unused. + */ +int +pr_unlock ( + register int pid /* process id */ +) +{ + struct process *pr; + + if (pid != NULL) + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) + if (pr->pr_pid == pid) + return (pr->pr_flags &= ~P_LOCKED); + + return (ERR); +} + + +/* PR_LISTCACHE -- Info command, used to display the contents of the process + * cache. Format: pid [RH][L] process_name + */ +void +pr_listcache ( + FILE *fp /* output file */ +) +{ + register struct process *pr; + + pr_checkup(); + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) + if (pr->pr_pid) { + int os_pid; + char nodename[SZ_FNAME+1]; + char out[100]; + + /* Print out pid in both decimal and hex, since the host + * system might need either. Also print the VOS pid since + * that is what is needed for flprcache (although flprcache + * will accept a task name instead). Note that c_kimapchan + * must be called to get the host PID if networking is in use. + */ + os_pid = c_kimapchan (pr->pr_pid, nodename, SZ_FNAME); + sprintf (out, "[%02d] %s!%d(%xX)", + pr->pr_pid, nodename, os_pid, os_pid); + fprintf (fp, " %-32s %c%c %s\n", + out, + (pr->pr_flags&P_ACTIVE) ? 'R' : 'H', + (pr->pr_flags&P_LOCKED) ? 'L' : ' ', + pr->pr_name); + + } else { + fprintf(fp, "%12d", 0); + fputc ('\n',fp); + } +} + + +/* PR_DUMPCACHE -- Disconnect the named process, or disconnect all processes + * currently running in the cache, and clear the process tables. A count of + * the number of active processes not disconnected is returned as the function + * value. Locks may be forced if desired, i.e., when dumping the cache prior + * to process termination. + */ +void +pr_dumpcache (int pid, int break_locks) +{ + register struct process *pr; + register int n; + + pr_checkup(); + + /* Do not traverse list using list pointers, because the first + * pr_disconnect will leave process pr at the tail of the list, + * causing premature termination. + */ + for (pr=pr_cache, n=sz_prcache; --n >= 0; pr++) + if ((pid == 0 && pr->pr_pid) || (pid == pr->pr_pid)) { + if (break_locks && pr_idle(pr)) + pr->pr_flags &= ~P_LOCKED; + pr_pdisconnect (pr); + } + + if (break_locks) + pr_pno = 1; +} + + +/* PR_PRUNECACHE -- Disconnect all processes currently running in the cache + * for which the process number is greater than that given, i.e., which were + * connected since the given PNO was assigned. Locked processes are not + * affected. + */ +void +pr_prunecache (int pno) +{ + register struct process *pr; + register int n; + + pr_checkup(); + + /* Do not traverse list using list pointers, because the first + * pr_disconnect will leave process pr at the tail of the list, + * causing premature termination. + */ + for (pr=pr_cache, n=sz_prcache; --n >= 0; pr++) + if (pr->pr_pid && pr->pr_pno > pno) + pr_pdisconnect (pr); +} + + +/* PR_GETPNO -- Get the next process number. These are supposed to be returned + * in time order. If 10 million processes are spawned without setcache being + * called, the counter might wrap around, but that does not seem likely and is + * harmless in any case. + */ +int +pr_getpno (void) +{ + return (pr_pno++); +} + + +/* PR_PNAMETOPID -- Lookup the named process in the cache and return the pid + * if found, NULL otherwise. + */ +int +pr_pnametopid (char *pname) +{ + register struct process *pr; + + pr_checkup(); + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) + if (strcmp (pr->pr_name, pname) == 0) + return (pr->pr_pid); + + return (NULL); +} + + +/* PR_CHDIR -- Change the current working directory of a child process, or + * of all connected but idle processes if pid=0. + */ +void +pr_chdir (register int pid, char *newdir) +{ + register struct process *pr; + + pr_checkup(); + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) { + if (pr->pr_pid == NULL || !pr_idle(pr)) + continue; + else if (pid == NULL || pr->pr_pid == pid) + c_prchdir (pr->pr_pid, newdir); + } +} + + +/* PR_ENVSET -- Set the value of an environment variable in a child process, + * or in all connected but idle processes if pid=0. + */ +void +pr_envset (register int pid, char *envvar, char *valuestr) +{ + register struct process *pr; + + pr_checkup(); + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) { + if (pr->pr_pid == NULL || !pr_idle(pr)) + continue; + else if (pid == NULL || pr->pr_pid == pid) + c_prenvset (pr->pr_pid, envvar, valuestr); + } +} + + +/* PR_CHECKUP -- Check on the status of all connected child processes to see + * if any have died. If a process has died we must disconnect the process + * to free file descriptors and the process cache slot. + */ +void +pr_checkup (void) +{ + register struct process *pr; + register int n; + + /* Do not traverse list using list pointers, because the first + * pr_disconnect will leave process pr at the tail of the list, + * causing premature termination. + */ + for (pr=pr_cache, n=sz_prcache; --n >= 0; pr++) { + if (pr->pr_pid != NULL) { + if (c_prstati (pr->pr_pid, PR_STATUS) == P_DEAD) { + pr->pr_flags = 0; + pr_pdisconnect (pr); + } + } + } +} + + +/* ONIPC -- Call this when get a signal that indicates a write to an IPC + * channel with no reader. We are called after the system X_IPC handler + * has been called to cleanup the internal process tables and file system, + * disabling any further output to the process. + */ +/* ARGSUSED */ +void +onipc ( + int *vex, /* virtual exception code */ + PFI *next_handler /* next handler to be called */ +) +{ + register struct process *pr; + + for (pr=pr_head; pr != NULL; pr=pr->pr_dn) { + if (pr->pr_pid != NULL) { + if (c_prstati (pr->pr_pid, PR_STATUS) == P_DEAD) + break; + } + } + + cl_error (E_UERR, "Abnormal termination of child process '%s'", + pr ? pr->pr_name : "??"); +} + + +/* PR_INITCACHE -- Initialize the process cache, i.e., set up the queue for the + * first time. The minimum cache size is 2. + */ +void +pr_initcache (void) +{ + register struct process *pr; + register int n; + + for (pr=pr_cache, n=MAXSUBPROC; --n >= 0; pr++) { + pr->pr_pid = 0; + pr->pr_flags = 0; + pr->pr_pno = 0; + pr->pr_in = pr->pr_out = NULL; + pr->pr_up = pr->pr_dn = NULL; + } + + pr_head = pr_tail = pr_cache; + for (n=1; n < sz_prcache; n++) + pr_tohead (&pr_cache[n]); + + pr_pno = 1; +} + + +/* PR_TOHEAD -- Relink a process at the head of the cache list. + */ +static void +pr_tohead (struct process *pr) +{ + if (pr_head != pr) { + pr_unlink (pr); + pr->pr_dn = pr_head; + pr->pr_up = NULL; + pr_head->pr_up = pr; + pr_head = pr; + } +} + + +/* PR_TOTAIL -- Relink a process at the tail of the cache list. + */ +static void +pr_totail (struct process *pr) +{ + if (pr_tail != pr) { + pr_unlink (pr); + pr->pr_up = pr_tail; + pr->pr_dn = NULL; + pr_tail->pr_dn = pr; + pr_tail = pr; + } +} + + +/* PR_UNLINK -- Unlink a process from the list. + */ +static void +pr_unlink (struct process *pr) +{ + if (pr->pr_up) { + (pr->pr_up)->pr_dn = pr->pr_dn; + if (pr == pr_tail) + pr_tail = pr->pr_up; + } + + if (pr->pr_dn) { + (pr->pr_dn)->pr_up = pr->pr_up; + if (pr == pr_head) + pr_head = pr->pr_dn; + } +} |