[comp.lang.perl] do_select

grady@fxgrp.fx.com (Steven Grady) (12/16/89)

do_select(), the function that implements UNIX select(), does not work
properly.  There are two problems.  First, it uses the length of the
vector (represented as a string internally) as the width of the bit
field passed to select().  Unfortunately, it is including the '\0'
terminator of the string as part of the length.  With the width off by
8, the file descriptors examined are 8 higher than they should be.

Second and more important, it does not work on suns.  It does not use
the FD_* macros.  The representation it uses for the file descriptor
bits, namely, a simple string, does not work at all (because the width
on a sun must be a multiple of 32 bits).

Here is a patch to fix do_select().  It is not very pretty, but it gets
the job done.  There is no compile flag to enable it, since it is not a
complete patch anyway.  This version will work on suns, but it won't
work, for instance, on AIX (which uses "sellist" structures).  The
"correct" way to do things would be to avoid explicitly using perl
vecs.  I'd like to see a different interface to select() anyway.
Perhaps something like:

    (@readready, @writeready, @exceptready, $timeleft) = 
	select(@readhandles, @writehandles, @excepthandles, $timeout);

where each of the arrays would be an array of expressions evaluating to
file handles.  The vec interface is pretty bogus (and doesn't work,
under the current implementation).

*** doio.c	Fri Dec 15 20:26:16 1989
--- doio.c.BAK	Fri Dec 15 20:07:44 1989
***************
*** 1510,1537 ****
      else
  	tbuf = Null(struct timeval*);
  
!     {
! 	fd_set fds[4];
! 	register int i, j;
! 	for (j = 1; j <= 3; j++) {
! 	    FD_ZERO(&fds[j]);
! 	    if (st[sp+j]->str_ptr == NULL) continue;
! 	    for (i = 0; i < (maxlen - 1) * 8; i++) {
! 		if (st[sp+j]->str_ptr[i/8] & (1 << (i % 8)))
! 		    FD_SET(i, &fds[j]);
! 	    }
! 	}
! 	nfound = select(
! 	    (maxlen - 1) * 8, &fds[1], &fds[2], &fds[3], tbuf);
! 	for (j = 1; j <= 3; j++) {
! 	    if (st[sp+j]->str_ptr == NULL) continue;
! 	    bzero(st[sp+j]->str_ptr, (maxlen - 1));
! 	    for (i = 0; i < (maxlen - 1) * 8; i++) {
! 		if (FD_ISSET(i, &fds[j]))
! 		    st[sp+j]->str_ptr[i/8] |= (1 << (i % 8));
! 	    }
! 	}
!     }
  
      st[++sp] = str_static(&str_no);
      str_numset(st[sp], (double)nfound);
--- 1510,1521 ----
      else
  	tbuf = Null(struct timeval*);
  
!     nfound = select(
! 	maxlen * 8,
! 	st[sp+1]->str_ptr,
! 	st[sp+2]->str_ptr,
! 	st[sp+3]->str_ptr,
! 	tbuf);
  
      st[++sp] = str_static(&str_no);
      str_numset(st[sp], (double)nfound);
-- 
	Steven
	...!ucbvax!grady
	grady@postgres.berkeley.edu

   Spock was waiting for them when they got to the conference
room.  "Captain, I've run the data we collected through the
computer."
   "Well, Spock, you must be a very proud young man.  So what's
the deal with these council weasels?"

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (12/19/89)

In article <1989Dec16.044106.7924@fxgrp.fx.com> grady@fxgrp.fx.com (Steven Grady) writes:
: do_select(), the function that implements UNIX select(), does not work
: properly.  There are two problems.  First, it uses the length of the
: vector (represented as a string internally) as the width of the bit
: field passed to select().  Unfortunately, it is including the '\0'
: terminator of the string as part of the length.  With the width off by
: 8, the file descriptors examined are 8 higher than they should be.

This is slightly misleading.  The problem is that it was basing the length
of the vector on the allocated length of the string rather than the
current length of the string.  There's nothing to prevent the allocated
length from being any old number greater than the current length.
The correct patch for this is not to subtract 8 bits from the length,
but to base the length on str_cur rather than str_len.

: Second and more important, it does not work on suns.  It does not use
: the FD_* macros.  The representation it uses for the file descriptor
: bits, namely, a simple string, does not work at all (because the width
: on a sun must be a multiple of 32 bits).

Yes, it's basically a byte reordering problem.

: Here is a patch to fix do_select().  It is not very pretty, but it gets
: the job done.  There is no compile flag to enable it, since it is not a
: complete patch anyway.  This version will work on suns, but it won't
: work, for instance, on AIX (which uses "sellist" structures).

I've got a do_select for patch 7 that should work everwhere that uses
bitmaps composed of ints or longs.  Even places that don't have FD_SET(), etc.
(if there are any such).  Someone will have to tell me about "sellist"
structures, though.

Patch 7 should be out later this week.

: The
: "correct" way to do things would be to avoid explicitly using perl
: vecs.  I'd like to see a different interface to select() anyway.
: Perhaps something like:
: 
:     (@readready, @writeready, @exceptready, $timeleft) = 
: 	select(@readhandles, @writehandles, @excepthandles, $timeout);
: 
: where each of the arrays would be an array of expressions evaluating to
: file handles.  The vec interface is pretty bogus (and doesn't work,
: under the current implementation).

I respectfully disagree.  The proposed interface would be very difficult
to make efficient.  Several net.people will attest that I thought long
and hard about the interface to select (pun not intended).  I considered
and rejected quite a few interfaces.

On top of which, your arguments are inconsistent with the way LISTs work.
And the assignment to multiple arrays would cause all the array values
to be assigned to the first array, or I'd have to special case assignments
that come from select, yech.

And I also disagree that vec() interface is "pretty bogus".  I basically
get to still two kurds with one bone: first, I get a representation of an
fd set that is easy to pass to the system call select() without much
processing; second, I get a general facility for doing bitmaps, which I want
for other reasons (like article sets in a newsreader :-).

If you want to do it the "correct" but slow way, here's a subroutine that
has approximately the interface you desire.  It differs in that the
ready filehandles are returned in the associative array of the same name.

;# Usage:
;#	@readmap = ('STDIN','FOO','BAR');
;#	@writemap = ('STDOUT','STDERR');
;#	@exceptmap = (@readmap,@writemap);
;#
;#	$nfound = &select(*readmap,*writemap,*exceptmap,$timeout);
;#		or
;#	($nfound,$timeleft) = &select(*readmap,*writemap,*exceptmap,$timeout);
;#
;#	if ($nfound) {
;#	    if ($readmap{'stdin'}) {

sub select {
    local(*r,*w,*e,$timeout) = @_;
    local($nfound,$rmap,$wmap,$emap);

    foreach $handle (@r) {
	vec($rmap,fileno($handle),1) = 1;
    }
    foreach $handle (@w) {
	vec($wmap,fileno($handle),1) = 1;
    }
    foreach $handle (@e) {
	vec($emap,fileno($handle),1) = 1;
    }

    ($nfound,$timeout) = select($rmap,$wmap,$emap,$timeout);

    %r = (); %w = (); %e = ();
    foreach $handle (@r) {
	$r{$handle} = 1 if vec($rmap,fileno($handle),1);
    }
    foreach $handle (@w) {
	$w{$handle} = 1 if vec($wmap,fileno($handle),1);
    }
    foreach $handle (@e) {
	$e{$handle} = 1 if vec($emap,fileno($handle),1);
    }

    if (wantarray) {
	return $nfound,$timeout;
    }
    else {
	return $nfound;
    }
}

Or some such.  I haven't tested it.

By the by, I don't know of any systems that DO implement $timeleft yet.

Larry

mdb@kosciusko.esd.3com.com (Mark D. Baushke) (12/20/89)

On 19 Dec 89 07:04:01 GMT, lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) said:

Larry> I've got a do_select for patch 7 that should work everwhere
Larry> that uses bitmaps composed of ints or longs.  Even places that
Larry> don't have FD_SET(), etc.  (if there are any such).  

There are indeed machines without FD_SET(). For example, SunOS 3.x
does NOT have it. I believe that at least some versions Ultrix (e.g., 2.0)
also do not have it.

I have seen something like the following code used on SunOS 3.x and
Ultrix 2.0 machines as a workaround.

/*
 * Ultrix 2.0/SunOS 3.x File Descriptor Compatibility Macros
 *
 * BIG NOTE!!! This will only allow fd_sets of 32 bits (assuming that's
 * the size of an int). Also, SunOS 3.x only allows 30 FDs in any case.
 *
 */

#ifndef FD_SET

#define NBBY    8               /* number of bits in a byte */

#define	NFDBITS	(sizeof(int) * NBBY)

/*
 * This is for Ultrix 2.0 and SunOS 3.x where fd_sets only have 1 element.
*/

/*
	I'm assuming that the only use of howmany is 
	howmany(FD_SETSIZE, NFDBITS), which should always return 1.
	The following code will need to be uncommented otherwise.

#define howmany(x, y) (((x)+((y)-1))/(y))
*/

#define howmany(x,y) 1

#ifndef FD_SETSIZE
#define FD_SETSIZE	NFDBITS
#endif

#define FD_SET(n, p)    ((p)->fds_bits[0] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[0] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[0] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      ((p)->fds_bits[0] = 0)

#endif /* FD_SET */

Larry> Someone will have to tell me about "sellist" structures,
Larry> though.

Hope this helps,
-- 
Mark D. Baushke
mdb@ESD.3Com.COM