| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ====================================================================== | 
| 2 |  |  |  |  |  |  | # | 
| 3 |  |  |  |  |  |  | # Copyright (C) 2000 Lincoln D. Stein | 
| 4 |  |  |  |  |  |  | # Formatting changed to match the layout layed out in Perl Best Practices | 
| 5 |  |  |  |  |  |  | # (by Damian Conway) by Martin Kutter in 2008 | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | # ====================================================================== | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | package IO::SessionSet; | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 2 |  |  | 2 |  | 711 | use strict; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 68 |  | 
| 12 | 2 |  |  | 2 |  | 11 | use Carp; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 138 |  | 
| 13 | 2 |  |  | 2 |  | 2295 | use IO::Select; | 
|  | 2 |  |  |  |  | 4126 |  | 
|  | 2 |  |  |  |  | 109 |  | 
| 14 | 2 |  |  | 2 |  | 2650 | use IO::Handle; | 
|  | 2 |  |  |  |  | 20878 |  | 
|  | 2 |  |  |  |  | 118 |  | 
| 15 | 2 |  |  | 2 |  | 654 | use IO::SessionData; | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 52 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 2 |  |  | 2 |  | 12 | use vars '$DEBUG'; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 1946 |  | 
| 18 |  |  |  |  |  |  | $DEBUG = 0; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | # Class method new() | 
| 21 |  |  |  |  |  |  | # Create a new Session set. | 
| 22 |  |  |  |  |  |  | # If passed a listening socket, use that to | 
| 23 |  |  |  |  |  |  | # accept new IO::SessionData objects automatically. | 
| 24 |  |  |  |  |  |  | sub new { | 
| 25 | 1 |  |  | 1 | 0 | 4 | my $pack = shift; | 
| 26 | 1 |  |  |  |  | 2 | my $listen = shift; | 
| 27 | 1 |  |  |  |  | 11 | my $self = bless { | 
| 28 |  |  |  |  |  |  | sessions     => {}, | 
| 29 |  |  |  |  |  |  | readers      => IO::Select->new(), | 
| 30 |  |  |  |  |  |  | writers      => IO::Select->new(), | 
| 31 |  |  |  |  |  |  | }, $pack; | 
| 32 |  |  |  |  |  |  | # if initialized with an IO::Handle object (or subclass) | 
| 33 |  |  |  |  |  |  | # then we treat it as a listening socket. | 
| 34 | 1 | 50 | 33 |  |  | 25 | if ( defined($listen) and $listen->can('accept') ) { | 
| 35 | 0 |  |  |  |  | 0 | $self->{listen_socket} = $listen; | 
| 36 | 0 |  |  |  |  | 0 | $self->{readers}->add($listen); | 
| 37 |  |  |  |  |  |  | } | 
| 38 | 1 |  |  |  |  | 9 | return $self; | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | # Object method: sessions() | 
| 42 |  |  |  |  |  |  | # Return list of all the sessions currently in the set. | 
| 43 |  |  |  |  |  |  | sub sessions { | 
| 44 | 1 |  |  | 1 | 0 | 3 | return values %{shift->{sessions}} | 
|  | 1 |  |  |  |  | 6 |  | 
| 45 |  |  |  |  |  |  | }; | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | # Object method: add() | 
| 48 |  |  |  |  |  |  | # Add a handle to the session set.  Will automatically | 
| 49 |  |  |  |  |  |  | # create a IO::SessionData wrapper around the handle. | 
| 50 |  |  |  |  |  |  | sub add { | 
| 51 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 52 | 0 |  |  |  |  | 0 | my ($handle,$writeonly) = @_; | 
| 53 | 0 | 0 |  |  |  | 0 | warn "Adding a new session for $handle.\n" if $DEBUG; | 
| 54 | 0 |  |  |  |  | 0 | return $self->{sessions}{$handle} = | 
| 55 |  |  |  |  |  |  | $self->SessionDataClass->new($self,$handle,$writeonly); | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | # Object method: delete() | 
| 59 |  |  |  |  |  |  | # Remove a session from the session set.  May pass either a handle or | 
| 60 |  |  |  |  |  |  | # a corresponding IO::SessionData wrapper. | 
| 61 |  |  |  |  |  |  | sub delete { | 
| 62 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 63 | 0 |  |  |  |  | 0 | my $thing = shift; | 
| 64 | 0 |  |  |  |  | 0 | my $handle = $self->to_handle($thing); | 
| 65 | 0 |  |  |  |  | 0 | my $sess = $self->to_session($thing); | 
| 66 | 0 | 0 |  |  |  | 0 | warn "Deleting session $sess handle $handle.\n" if $DEBUG; | 
| 67 | 0 |  |  |  |  | 0 | delete $self->{sessions}{$handle}; | 
| 68 | 0 |  |  |  |  | 0 | $self->{readers}->remove($handle); | 
| 69 | 0 |  |  |  |  | 0 | $self->{writers}->remove($handle); | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | # Object method: to_handle() | 
| 73 |  |  |  |  |  |  | # Return a handle, given either a handle or a IO::SessionData object. | 
| 74 |  |  |  |  |  |  | sub to_handle { | 
| 75 | 1 |  |  | 1 | 0 | 2 | my $self = shift; | 
| 76 | 1 |  |  |  |  | 3 | my $thing = shift; | 
| 77 | 1 | 50 |  |  |  | 11 | return $thing->handle if $thing->isa('IO::SessionData'); | 
| 78 | 1 | 50 |  |  |  | 29 | return $thing if defined (fileno $thing); | 
| 79 | 1 |  |  |  |  | 5 | return;  # undefined value | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | # Object method: to_session | 
| 83 |  |  |  |  |  |  | # Return a IO::SessionData object, given either a handle or the object itself. | 
| 84 |  |  |  |  |  |  | sub to_session { | 
| 85 | 1 |  |  | 1 | 0 | 3 | my $self = shift; | 
| 86 | 1 |  |  |  |  | 2 | my $thing = shift; | 
| 87 | 1 | 50 |  |  |  | 11 | return $thing if $thing->isa('IO::SessionData'); | 
| 88 | 1 | 50 |  |  |  | 8 | return $self->{sessions}{$thing} if defined (fileno $thing); | 
| 89 | 1 |  |  |  |  | 4 | return;  # undefined value | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | # Object method: activate() | 
| 93 |  |  |  |  |  |  | # Called with parameters ($session,'read'|'write' [,$activate]) | 
| 94 |  |  |  |  |  |  | # If called without the $activate argument, will return true | 
| 95 |  |  |  |  |  |  | # if the indicated handle is on the read or write IO::Select set. | 
| 96 |  |  |  |  |  |  | # May use either a session object or a handle as first argument. | 
| 97 |  |  |  |  |  |  | sub activate { | 
| 98 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 99 | 0 |  |  |  |  | 0 | my ($thing,$rw,$act) = @_; | 
| 100 | 0 | 0 |  |  |  | 0 | croak 'Usage $obj->activate($session,"read"|"write" [,$activate])' | 
| 101 |  |  |  |  |  |  | unless @_ >= 2; | 
| 102 | 0 |  |  |  |  | 0 | my $handle = $self->to_handle($thing); | 
| 103 | 0 | 0 |  |  |  | 0 | my $select = lc($rw) eq 'read' ? 'readers' : 'writers'; | 
| 104 | 0 |  |  |  |  | 0 | my $prior = defined $self->{$select}->exists($handle); | 
| 105 | 0 | 0 | 0 |  |  | 0 | if (defined $act && $act != $prior) { | 
| 106 | 0 | 0 |  |  |  | 0 | $self->{$select}->add($handle)        if $act; | 
| 107 | 0 | 0 |  |  |  | 0 | $self->{$select}->remove($handle) unless $act; | 
| 108 | 0 | 0 |  |  |  | 0 | warn $act ? 'Activating' : 'Inactivating', | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | " handle $handle for ", | 
| 110 |  |  |  |  |  |  | $rw eq 'read' ? 'reading':'writing',".\n" if $DEBUG; | 
| 111 |  |  |  |  |  |  | } | 
| 112 | 0 |  |  |  |  | 0 | return $prior; | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | # Object method: wait() | 
| 116 |  |  |  |  |  |  | # Wait for I/O.  Handles writes automatically.  Returns a list of | 
| 117 |  |  |  |  |  |  | # IO::SessionData objects ready for reading. | 
| 118 |  |  |  |  |  |  | # If there is a listen socket, then will automatically do an accept() | 
| 119 |  |  |  |  |  |  | # and return a new IO::SessionData object for that. | 
| 120 |  |  |  |  |  |  | sub wait { | 
| 121 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 122 | 0 |  |  |  |  | 0 | my $timeout = shift; | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | # Call select() to get the list of sessions that are ready for | 
| 125 |  |  |  |  |  |  | # reading/writing. | 
| 126 | 0 | 0 |  |  |  | 0 | warn "IO::Select->select() returned error: $!" | 
| 127 |  |  |  |  |  |  | unless my ($read,$write) = | 
| 128 |  |  |  |  |  |  | IO::Select->select($self->{readers},$self->{writers},undef,$timeout); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | # handle queued writes automatically | 
| 131 | 0 |  |  |  |  | 0 | foreach (@$write) { | 
| 132 | 0 |  |  |  |  | 0 | my $session = $self->to_session($_); | 
| 133 | 0 | 0 |  |  |  | 0 | warn "Writing pending data (",$session->pending+0," bytes) for $_.\n" | 
| 134 |  |  |  |  |  |  | if $DEBUG; | 
| 135 | 0 |  |  |  |  | 0 | my $rc = $session->write; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | # Return list of sessions that are ready for reading. | 
| 139 |  |  |  |  |  |  | # If one of the ready handles is the listen socket, then | 
| 140 |  |  |  |  |  |  | # create a new session. | 
| 141 |  |  |  |  |  |  | # Otherwise return the ready handles as a list of IO::SessionData objects. | 
| 142 | 0 |  |  |  |  | 0 | my @sessions; | 
| 143 | 0 |  |  |  |  | 0 | foreach (@$read) { | 
| 144 | 0 | 0 |  |  |  | 0 | if ($_ eq $self->{listen_socket}) { | 
| 145 | 0 |  |  |  |  | 0 | my $newhandle = $_->accept; | 
| 146 | 0 | 0 |  |  |  | 0 | warn "Accepting a new handle $newhandle.\n" if $DEBUG; | 
| 147 | 0 | 0 |  |  |  | 0 | my $newsess = $self->add($newhandle) if $newhandle; | 
| 148 | 0 |  |  |  |  | 0 | push @sessions,$newsess; | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  | else { | 
| 151 | 0 |  |  |  |  | 0 | push @sessions,$self->to_session($_); | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | } | 
| 154 | 0 |  |  |  |  | 0 | return @sessions; | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | # Class method: SessionDataClass | 
| 158 |  |  |  |  |  |  | # Return the string containing the name of the session data | 
| 159 |  |  |  |  |  |  | # wrapper class.  Subclass and override to use a different | 
| 160 |  |  |  |  |  |  | # session data class. | 
| 161 | 1 |  |  | 1 | 0 | 766 | sub SessionDataClass {  return 'IO::SessionData'; } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | 1; |