[news.software.anu-news] Multithreaded NNTP server part 1/1

keith@and.cs.liv.ac.uk (Keith Halewood) (06/15/91)

By popular demand, here's the multithreaded nntp server interface for CMUTEK
TCP/IP V6.5.

Keith

$! ------------------ CUT HERE -----------------------
$ v='f$verify(f$trnlnm("SHARE_VERIFY"))'
$!
$! This archive created by VMS_SHARE Version 7.1-004  3-AUG-1989
$!   On 15-JUN-1991 15:51:54.91   By user KEITH 
$!
$! This VMS_SHARE Written by:
$!    Andy Harper, Kings College London UK
$!
$! Acknowledgements to:
$!    James Gray       - Original VMS_SHARE
$!    Michael Bednarek - Original Concept and implementation
$!
$! TO UNPACK THIS SHARE FILE, CONCATENATE ALL PARTS IN ORDER
$! AND EXECUTE AS A COMMAND PROCEDURE  (  @name  )
$!
$! THE FOLLOWING FILE(S) WILL BE CREATED AFTER UNPACKING:
$!       1. NNTP_TCPCMU_M.C;12
$!       2. README.TXT;2
$!
$set="set"
$set symbol/scope=(nolocal,noglobal)
$f=f$parse("SHARE_TEMP","SYS$SCRATCH:.TMP_"+f$getjpi("","PID"))
$e="write sys$error  ""%UNPACK"", "
$w="write sys$output ""%UNPACK"", "
$ if f$trnlnm("SHARE_LOG") then $ w = "!"
$ if f$getsyi("version") .ges. "V4.4" then $ goto START
$ e "-E-OLDVER, Must run at least VMS 4.4"
$ v=f$verify(v)
$ exit 44
$UNPACK: SUBROUTINE ! P1=filename, P2=checksum
$ if f$search(P1) .eqs. "" then $ goto file_absent
$ e "-W-EXISTS, File ''P1' exists. Skipped."
$ delete/nolog 'f'*
$ exit
$file_absent:
$ if f$parse(P1) .nes. "" then $ goto dirok
$ dn=f$parse(P1,,,"DIRECTORY")
$ w "-I-CREDIR, Creating directory ''dn'."
$ create/dir 'dn'
$ if $status then $ goto dirok
$ e "-E-CREDIRFAIL, Unable to create ''dn'. File skipped."
$ delete/nolog 'f'*
$ exit
$dirok:
$ w "-I-PROCESS, Processing file ''P1'."
$ define/user sys$output nl:
$ EDIT/TPU/NOSEC/NODIS/COM=SYS$INPUT 'f'/OUT='P1'
PROCEDURE Unpacker ON_ERROR ENDON_ERROR;SET(FACILITY_NAME,"UNPACK");SET(
SUCCESS,OFF);SET(INFORMATIONAL,OFF);f:=GET_INFO(COMMAND_LINE,"file_name");
buff:=CREATE_BUFFER(f,f);p:=SPAN(" ")@r&LINE_END;POSITION(BEGINNING_OF(buff))
;LOOP EXITIF SEARCH(p,FORWARD)=0;POSITION(r);ERASE(r);ENDLOOP;POSITION(
BEGINNING_OF(buff));g:=0;LOOP EXITIF MARK(NONE)=END_OF(buff);x:=
ERASE_CHARACTER(1);IF g = 0 THEN IF x="X" THEN MOVE_VERTICAL(1);ENDIF;IF x=
"V" THEN APPEND_LINE;MOVE_HORIZONTAL(-CURRENT_OFFSET);MOVE_VERTICAL(1);ENDIF;
IF x="+" THEN g:=1;ERASE_LINE;ENDIF;ELSE IF x="-" THEN g:=0;ENDIF;ERASE_LINE;
ENDIF;ENDLOOP;p:="`";POSITION(BEGINNING_OF(buff));LOOP r:=SEARCH(p,FORWARD);
EXITIF r=0;POSITION(r);ERASE(r);COPY_TEXT(ASCII(INT(ERASE_CHARACTER(3))));
ENDLOOP;o:=GET_INFO(COMMAND_LINE,"output_file");WRITE_FILE(buff,o);
ENDPROCEDURE;Unpacker;EXIT;
$ delete/nolog 'f'*
$ CHECKSUM 'P1'
$ IF CHECKSUM$CHECKSUM .eqs. P2 THEN $ EXIT
$ e "-E-CHKSMFAIL, Checksum of ''P1' failed."
$ ENDSUBROUTINE
$START:
$ create/nolog 'f'
X#module NNTP_MCMUTEK "V1.0"
X
X/* Multithreaded NNTP server for ANU_NEWS V6.x and CMU-TEK TCP/IP V6.5
X * By Keith Halewood. The 'getremhost' routine has been stolen from the
X * original single-thread server interface.
X */
X
X#include descrip
X#include iodef
X#include signal
X#include stdio
X
X#define PORT       119     /* Should be 119, use 8888 for testing. */
X#define MAXTHREADS 16      /* Maximum number of services - Make sure
X                            * process quotas are capable of handling`032
X                            * the number chosen.
X                            */
X#define TIMEOUT    1800    /* Connection inactivty timeout in seconds;
X                            * causes an SS$_ABORT on reads and closes
X                            * the connection. Not exactly in the spirit
X                            * of the NNTP protocol, but most NNTP clients
X                            * are just as ill-mannered.
X                            */
X#define BUFSIZE    1024    /* Constants for QIO buffer lengths and
X                            */
X#define LINESIZE   2048    /* the read line assembly buffer.
X                            */
X
X/* Event numbers used by AST completion routines. The are placed,
X * along with a thread number into the attention queue.
X */
X
X#define EV_NULL                     0
X#define EV_OPEN_COMPLETED           1
X#define EV_OPEN_ABORTED             2
X#define EV_READ_COMPLETED           3
X#define EV_READ_ABORTED             4
X#define EV_WRITE_COMPLETED          5
X#define EV_WRITE_ABORTED            6
X
X/* Is the thread in use (healthy) or unassigned. Unassigned is also
X * a state used during shutdown to control the discarding of entries
X * in the I/O queues.
X */
X
X#define ACT_UNASSIGNED              0
X#define ACT_HEALTHY                 1
X
X/* 'Next function' return codes, determines whether the 'next function' call
X * for a thread is activated on receipt of a line (command) or on receipt of
X * the file terminator (a single full-stop) or whether the thread is to be
X * closed down.
X */
X
X#define TYP_NO_INPUT                0
X#define TYP_CMD_INPUT               1
X#define TYP_FILE_INPUT              2
X
X/* Definitions for the passive open (listen) on PORT
X */
X
Xunsigned short listen_channel, listen_iosb`0914`093;
Xint active_count = 0;
Xint active_listener = 0;
X
X/* I/O queue definition.
X */
X
Xstruct line_block
X  `123
X    struct line_block *next;
X    char *line;
X  `125;
X
X/* Thread structures. `0910`093 isn't used.
X */
X
Xvolatile static struct thread_struct
X  `123
X    int active;
X    unsigned short channel;
X    unsigned short write_iosb`0914`093, read_iosb`0914`093;
X    char write_buffer`091BUFSIZE-1`093, read_buffer`091BUFSIZE-1`093;
X    char read_residue`091LINESIZE-1`093;
X    struct line_block *read_head, *read_tail, *write_head, *write_tail;
X    int write_in_progress;
X    int next_type;
X    int (*next_function)();
X  `125 threads`091MAXTHREADS+1`093;
X
X$DESCRIPTOR(ipdsc,"INET$DEVICE");
X
X/******************************************************************
X * Exteral routines from NNTP_SERVER                              *
X ******************************************************************/
X
Xint server_init();
Xvoid server_init_unit();
X
X/******************************************************************
X * Thread event queue routines - ast interlocked (ie. safe)       *
X ******************************************************************/
X
X#define MAXQUEUE 255                   /* Arbitrary but large */
X
Xvolatile static int queue_thread_array`091MAXQUEUE`093;
Xvolatile static int queue_event_array`091MAXQUEUE`093;
Xvolatile static int queue_count = 0;
Xvolatile static int queue_head = 0;
Xvolatile static int queue_tail = 0;
X
Xstatic int enqueue(thread,event)
Xint thread, event;
X`123
X  int result = 1;
X  (void)sys$setast(0);
X  if (queue_count == MAXQUEUE)
X  `123
X    result = 0;
X  `125
X  else
X  `123
X    queue_count++;
X    queue_thread_array`091queue_head`093 = thread;
X    queue_event_array`091queue_head++`093 = event;
X    if (queue_head==MAXQUEUE) queue_head = 0;
X  `125
X  (void)sys$setast(1);
X  return(result);
X`125
X
Xstatic int dequeue(thread,event)
Xint *thread, *event;
X`123
X  int result = 1;
X  (void)sys$setast(0);
X  if (queue_count > 0)
X  `123
X    *thread = queue_thread_array`091queue_tail`093;
X    *event = queue_event_array`091queue_tail++`093;
X    queue_count--;
X    if (queue_tail==MAXQUEUE) queue_tail = 0;
X  `125 else
X  `123
X    result = 0;
X  `125
X  (void)sys$setast(1);
X  return(result);
X`125
X
X/* Convenient way of duplicating a string
X */
X
Xchar *strdup(s)
Xchar *s;
X`123
X  char *t = (char *)malloc(sizeof(char)*(strlen(s)+1));
X  strcpy(t,s);
X  return(t);
X`125
X
X/***************************************************************
X * AST handlers                                                *
X ***************************************************************/
X
Xvoid ast_signal_open_complete(dummy)
Xint dummy;
X`123
X  (void)enqueue(0,(listen_iosb`0910`093&1) ? EV_OPEN_COMPLETED : EV_OPEN_ABO
VRTED);
X  (void)sys$wake(0,0);
X`125
X
Xvoid ast_signal_read_complete(thread)
Xint thread;
X`123
X  if (threads`091thread`093.active==ACT_HEALTHY)
X  `123
X    (void)enqueue(thread, (threads`091thread`093.read_iosb`0910`093&1) ?
X                          EV_READ_COMPLETED : EV_READ_ABORTED);
X    (void)sys$wake(0,0);
X  `125
X`125
X
Xvoid ast_signal_write_complete(thread)
Xint thread;
X`123
X  if (threads`091thread`093.active==ACT_HEALTHY)
X  `123
X    (void)enqueue(thread, (threads`091thread`093.write_iosb`0910`093&1) ?
X                          EV_WRITE_COMPLETED : EV_WRITE_ABORTED );
X    (void)sys$wake(0,0);
X  `125
X`125
X
X/***************************************************************
X * Read/Write queueing routines                                *
X ***************************************************************/
X
Xstatic void read_thread(thread)
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  int status;
X  /* IO$_READVBLK reads a block of data (upto BUFSIZE) from an
X   * open channel as in I/O users guide.
X   */
X  status = sys$qio(0,t->channel,IO$_READVBLK,t->read_iosb,
X                   ast_signal_read_complete,thread,
X                   t->read_buffer,BUFSIZE,0,0,0,0);
X  if (!(status&1)) (void)enqueue(thread,EV_READ_ABORTED);
X`125
X
Xstatic void write_thread(thread)
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  struct line_block *b = t->write_head;
X  int status;
X  if ((t->active==ACT_HEALTHY) && (t->write_in_progress==0))
X  `123
X    if (b!=NULL)
X    `123
X      strcpy(t->write_buffer,b->line);
X      t->write_head = b->next;
X      free(b->line);
X      free(b);
X      /* IO$_WRITEVBLK writes a block of data (length in P2) to an
X       * open channel as in I/O users guide.
X       */
X      status = sys$qio(0,t->channel,IO$_WRITEVBLK,t->write_iosb,
X                       ast_signal_write_complete,thread,
X                       t->write_buffer,strlen(t->write_buffer),0,0,0,0);
X      t->write_in_progress = status&1;
X      if (!(status&1)) (void)enqueue(thread,EV_WRITE_ABORTED);
X    `125
X  `125
X`125
X
X/***********************************************************************
X * Inits                                                               *
X ***********************************************************************/
X
Xstatic void init_thread(thread,channel)
Xint thread;
Xunsigned short channel;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  t->active = ACT_HEALTHY;
X  t->channel = channel;
X  t->read_head = NULL; t->read_tail = NULL;
X  t->write_head = NULL; t->write_tail = NULL;
X  t->write_in_progress = 0;
X  server_init_unit(thread);
X  read_thread(thread);
X  write_thread(thread);
X`125
X
Xstatic int find_clear_thread()
X`123
X  int i;
X  for (i=1;i<=MAXTHREADS;i++)
X    if (threads`091i`093.active==ACT_UNASSIGNED) return(i);
X  return(0);
X`125
X
Xstatic void init_listener()
X`123
X  int status;
X  active_listener = 0;
X  status = sys$assign(&ipdsc,&listen_channel,0,0);
X  if (!(status&1)) return;
X  /* IO$_CREATE in this instance opens a passive listen on PORT
X   * the listen as well as the future established connection on this
X   * channel will timeout after TIMEOUT second's worth of inactivity
X   * and send an SS$_ABORT to any queued I/O operations on the channel.
X   */
X  status = sys$qio(0,listen_channel,IO$_CREATE,listen_iosb,
X           ast_signal_open_complete,0,0,0,PORT,0,0,TIMEOUT);
X  if (!(status&1))
X  `123
X    (void)enqueue(0,EV_OPEN_ABORTED);
X  `125
X  else
X  `123
X    active_listener = 1;
X  `125
X`125
X
Xstatic void init_threads()
X`123
X  struct thread_struct *t;
X  int c;
X  active_count = 0;
X  for (t=threads,c=0;c<MAXTHREADS;t++,c++) t->active = ACT_UNASSIGNED;
X`125
X
Xstatic void init()
X`123
X  if (!server_init(MAXTHREADS)) exit(1);
X  init_threads();
X  init_listener();
X`125
X
X/***********************************************************************
X * Active routines                                                     *
X ***********************************************************************/
X
Xstatic void enqueue_reader(thread,string)
Xint thread;
Xchar *string;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  struct line_block *b;
X  if (t->active==ACT_HEALTHY)
X  `123
X    b = (struct line_block *) malloc(sizeof(struct line_block));
X    b->next = NULL;
X    b->line = strdup(string);
X    if (t->read_head!=NULL)
X    `123
X      t->read_tail->next = b;
X      t->read_tail = b;
X    `125
X    else
X    `123
X      t->read_tail = b;
X      t->read_head = b;
X    `125
X    if ((t->next_type==TYP_CMD_INPUT) `124`124
X        ((t->next_type==TYP_FILE_INPUT) && !strcmp(string,".")))
X    `123
X      (t->next_function)(thread);
X      write_thread(thread);
X    `125
X  `125
X`125
X
Xstatic void abort_thread(thread)
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  struct line_block *b;
X  if (t->active != ACT_UNASSIGNED)
X  `123
X    t->active = ACT_UNASSIGNED;
X    b = t->read_head;
X    while(b!=NULL)
X    `123
X      struct line_block *c = b;
X      b=b->next;
X      free(c->line);
X      free(c);
X    `125
X    t->read_head = NULL; t->read_tail = NULL;
X    b = t->write_head;
X    while(b!=NULL)
X    `123
X      struct line_block *c = b;
X      b=b->next;
X      free(c->line);
X      free(c);
X    `125
X    t->write_head = NULL; t->write_tail = NULL;
X    if (t->next_type==TYP_FILE_INPUT) enqueue_reader(thread,".");
X    if (t->next_type==TYP_CMD_INPUT) enqueue_reader(thread,"QUIT");
X    (void)sys$dassgn(t->channel);
X    active_count--;
X    if (active_count<=0) exit(1);
X  `125
X`125
X
Xstatic void close_thread(thread)
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  /* IO$_DELETE closes the connection after ensuring that all data
X   * transfer in progress has reached its destinations.
X   */
X  (void)sys$qiow(0,t->channel,IO$_DELETE,t->write_iosb,0,0,0,0,0,0,0,0);
X  (void)sys$dassgn(t->channel);
X  active_count--;
X  if (active_count<=0) exit(1);
X`125
X
X
Xstatic void manage_open()
X`123
X  int thread, status;
X  char resource_msg`091`093 = "400 Server resource limits exceeded.\r\n";
X  active_listener = 0;
X  thread = find_clear_thread();
X  if (thread>0)
X  `123
X    init_thread(thread,listen_channel);
X    active_count++;
X    init_listener();
X  `125
X  else
X  `123
X    status = sys$qiow(0,listen_channel,IO$_WRITEVBLK,listen_iosb,0,0,
X                      resource_msg,strlen(resource_msg),0,0,0,0);
X    status = sys$qiow(0,listen_channel,IO$_DELETE,listen_iosb,0,0,0,0,0,0,0,
V0);
X    (void)sys$dassgn(listen_channel);
X    init_listener();
X  `125
X`125
X
Xstatic void manage_failed_open()
X`123
X  (void)sys$dassgn(listen_channel);
X  active_listener = 0;
X  if (active_count<=0) exit(1);
X  init_listener();
X`125
X
Xstatic void manage_read_completion(thread)
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  char *cp;
X  t->read_buffer`091t->read_iosb`0911`093`093='\0';
X  strcat(t->read_residue,t->read_buffer);
X  while (t->next_type!=TYP_NO_INPUT &&`032
X           (cp = strchr(t->read_residue,'\n'))!=NULL)
X  `123
X    *cp='\0'; if (cp`091-1`093=='\r') cp`091-1`093='\0'; cp++;
X    enqueue_reader(thread,t->read_residue);
X    strcpy(t->read_residue,cp);
X  `125
X  if (t->next_type!=TYP_NO_INPUT) read_thread(thread);
X`125
X
Xstatic void manage_failed_read(thread)
Xint thread;
X`123
X  abort_thread(thread);
X`125
X
Xstatic void manage_failed_write(thread)
Xint thread;
X`123
X  abort_thread(thread);
X`125
X
Xstatic void manage_write_completion(thread)
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  t->write_in_progress = 0;
X  if (t->write_head)
X  `123
X    write_thread(thread);
X  `125
X  else
X  `123
X    if (t->next_type==TYP_NO_INPUT) close_thread(thread);
X  `125
X`125
X
Xstatic void drain_queue()
X`123
X  int thread, event;
X  while (dequeue(&thread,&event))
X  `123
X    switch (event)
X    `123
X       case EV_OPEN_COMPLETED:  manage_open(); break;
X       case EV_OPEN_ABORTED:    manage_failed_open(); break;
X       case EV_READ_COMPLETED:  manage_read_completion(thread); break;
X       case EV_READ_ABORTED:    manage_failed_read(thread); break;
X       case EV_WRITE_COMPLETED: manage_write_completion(thread); break;
X       case EV_WRITE_ABORTED:   manage_failed_write(thread); break;
X    `125
X  `125
X`125
X
Xmain()
X`123
X  init();
X  for (;;)
X  `123
X    drain_queue();
X    (void)sys$hiber();
X  `125
X`125
X
X/*************************************************************************
X * NNTP_SERVER interface routines                                        *
X *************************************************************************/
X
Xint read_net(string,size,thread)
Xchar *string;
Xint size, thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  struct line_block *b = t->read_head;
X  if (b)
X  `123
X    strcpy(string,b->line); strcat(string,"\n");
X    t->read_head = b->next;
X    free(b->line);
X    free(b);
X    return(0);
X  `125
X  return(1);
X`125
X
Xint write_net(string,thread)
Xchar *string;
Xint thread;
X`123
X  struct thread_struct *t = &threads`091thread`093;
X  struct line_block *b;
X  if (t->active==ACT_HEALTHY)
X  `123
X    b = (struct line_block *) malloc(sizeof(struct line_block));
X    b->next = NULL;
X    b->line = strdup(string);
X    if (t->write_head)
X    `123
X      t->write_tail->next = b;
X      t->write_tail = b;
X    `125
X    else
X    `123
X      t->write_tail = b;
X      t->write_head = b;
X    `125
X  `125
X`125
X
Xint next_call(thread,func,type)
Xint thread;
Xint (*func)();
Xint type;
X`123
X  threads`091thread`093.next_function = func;
X  threads`091thread`093.next_type = type;
X`125
X
Xvoid getremhost(remhost,remuser,thread)
Xchar *remhost, *remuser;
Xint thread;
X`123
Xstruct ib `123
X    unsigned char fhost_len;
X    unsigned char lhost_len;
X    char fhost`091128`093;
X    short fill;
X    unsigned short fport;
X    short fill_1;
X    char lhost`091128`093;
X    unsigned short lport;
X    short fill_2;
X    unsigned int linet_addr;
X    unsigned int finet_addr;
X    `125 _align(quadword) info_buffer;
X  unsigned short iosb`0914`093;
X  int status;
X  strcpy(remuser,"nntp");
X  /* IO$_MODIFY retrieves connection information from the active channel
X   * specified. See struct ib above for the format of the received message.
X   */
X  status = sys$qiow(0,threads`091thread`093.channel,IO$_MODIFY,iosb,0,0,&inf
Vo_buffer,
X                    sizeof(info_buffer),
X                    0,0,0,0);
X
X  if (!(status&1))
X  `123
X    strcpy(remhost,"unknown");
X    return;
X  `125
X  if (!(iosb`0910`093&1))
X  `123
X    strcpy(remhost,"unknown");
X    return;
X  `125
X  strncpy(remhost,info_buffer.fhost,info_buffer.fhost_len);
X  remhost`091info_buffer.fhost_len`093='\0';
X  if (!*remhost)
X    sprintf(remhost,"%d.%d.%d.%d",
X                (info_buffer.finet_addr) & 0xff,
X                (info_buffer.finet_addr >> 8) & 0xff,
X                (info_buffer.finet_addr >> 16) & 0xff,
X                (info_buffer.finet_addr >> 24) & 0xff);
X`125
$ CALL UNPACK NNTP_TCPCMU_M.C;12 1162081336
$ create/nolog 'f'
XNNTP_TCPCMU_M - a multi-threaded nntp server interface for CMU-TEK IP/TCP 6.
V5.
X
XSome points to note:
X
X1) It would appear (although I haven't checked) that when a detached process
V is
Xcreated, ie. a WKS process, SYS$SCRATCH is not defined. Consequently, the
Xroutine sys_remote_send in NEWSDIST.C will fail and so will any attempt to
Xdistribute NNTP POSTed beyond the server's database. I've modified NEWSDIST.
VC
Xso that all references to SYS$SCRATCH (one of them) have been changed to
XNEWS_MANAGER. This is reasonable since file creation at these points is done
Xwith SYSPRV enabled.
X
X2) The server interface 'simulates' the 'declaration' of the process as a
Xnetwork object (c.f. DECnet and PSI) by always maintaining a passive listen
V on
Xport 119. On successful completion of the passive listen, the channel is han
Vded
Xto thread code and another passive listen is created. It is conceivable that
Xanother call could be made during the short period when a listen ISN'T in
Xprogress - this will cause the IP_ACP to fire-up another server process. I
Xhaven't added any code to check for multiple occurances of such processes (s
Vuch
Xas using locks, etc).
X
X3) Passive listens are maintained when MAXTHREADS is achieved. Calls are
Xaccepted so that an NNTP service failure message can be sent. The call is th
Ven
Xclosed and the passive listen re-established. This behaviour could be change
Vd
Xso that when MAXTHREADS is achieved, further passive listens are disabled,
Xenabling the IP_ACP to fire-off more server processes.
X
X4) Idle timeouts are used (IO$_CREATE parameter) to clear forgotten
Xconnections. The only problem with this is that an idle connection is closed
Xwithout being able to send an NNTP service failure message. So far, I haven'
Vt
Xencountered any problems with this - rtass and rrn (the clients mostly used
V at
Xour site) re-establish contact without problems. Also, clients are often jus
Vt
Xas rude, rather than sending a QUIT, they often tend to just abort the
Xconnection.
X
X5) When the number of active threads falls back down to zero, the server
Xterminates immediately rather than loitering for further potential connectio
Vns.
XI find this behaviour acceptable, others may not.
X
X6) The server interface buffers an entire transaction for each active thread
Xbecause of the presumed limitations of mixing calls to malloc and free in AS
VT
Xcontext (ie, I couldn't get it to work). Consequently, the stream of data
Xfollowing a POST command will be buffered on the heap until "." is received
V and
Xonly then will the next_function for that thread be called. Similarly, comma
Vnds
Xsuch as LIST will have their outputs buffered on the heap until their functi
Von
Xcompletes. Just make sure that the process has enough PGFLQUOTA.
X
X7) This code is free of debug and trace facilities. To debug this I tend to
Xrely entirely upon VAX DEBUG and VMXED. The code works well as far as I can
Xtell, errors are handled by abandoning the threads - no in-depth analysis of
Xexception codes is performed and no recovery is attempted within the confine
Vs
Xof the current thread.
X
X8) The server code is compiled and linked as for the single-threaded version
X(NNTP_TCPCMU.C).
X
X9) This is the line we have in our INTERNET.CONFIG for the multi-threaded
Xserver. It appears to suffice:
X
XWKS:119:NEWSRV:TCP$NEWSRV:NETWRK:NETMBX,TMPMBX,PHY_IO,SYSPRV,SYSLCK:\
XBYTLM=65535,BIOLM=32767,DIOLM=32767,ASTLM=200,ENQLM=100:\
XSYS$NULL:SYS$NULL:SYS$NULL:4:5
X
Xwhere TCP$NEWSRV is defined as NEWS_IMAGE:NNTP_TCPCMU_M.EXE
Xand   SYS$NULL is defined as NLA0:
XNote that SYSLCK privilege is needed due to rather strange behaviour on the
Xpart of one of our mail protocol handlers - you probably won't need this.
X
XI'd be very grateful if users of this code would send any fixes, improvement
Vs,
Xetc. to me as well as to the net or wherever.
X
XKeith
X--
XKeith Halewood           Janet: keith@uk.ac.liv.cs.and
XDept. Computer Science,  Internet: keith@and.cs.liv.ac.uk
XLiverpool University.
X"If they're the only survivors of a nuclear holocaust then they can't be in
Xvery good shape." - Beverly Crusher, ST:TNG
$ CALL UNPACK README.TXT;2 1664621481
$ v=f$verify(v)
$ EXIT
-- 
Keith Halewood           Janet: keith@uk.ac.liv.cs.and
Dept. Computer Science,  Internet: keith%and.cs.liv.ac.uk@nsfnet-relay.ac.uk
Liverpool University.    UUCP: ..!mcsun!ukc!liv-cs!keith
"If they're the only survivors of a nuclear holocaust then they can't be in
very good shape." - Beverly Crusher, ST:TNG