Ae internals
-
Upload
mnikolenko -
Category
Technology
-
view
1.267 -
download
0
Transcript of Ae internals
AnyEventInternals(samples of async programming)
Mons Anderson
AnyEvent::Internals
AE::*
use AE 5;
my $w = AE::io $fh, 0, sub { warn '$fh is readable'; };my $w = AE::io $fh, 1, sub { warn '$fh is writable'; };
my $w = AE::timer 1,0, sub { warn '1 second passed'; };my $w = AE::timer 0,1, sub { warn 'every 1 second'; };
my $w = AE::signal TERM => sub { warn 'TERM received'; };
my $w = AE::idle { warn 'Event loop is idle';};
I/OWaitSigMisc
AnyEvent::Internals
AE::cv
use AE 5;
my $cv = AE::cv;
any sub { async sub { calls sub { $cv->send; }; };};
$cv->recv; # Run loop until get send
AnyEvent::Internals
AE::cv
use AE 5;
my $cv = AE::cv { warn "All done" };
for (1..10) { $cv->begin; async_call sub { may_be_nested sub { $cv->end; } }}
AnyEvent::Internals
AE::HTTP
use AnyEvent::HTTP;my $cv = AE::cv;http_request GET => 'http://www.google.ru', sub { my ($body,$hdr) = @_; warn "$hdr->{Status}: $hdr->{Reason}\n"; warn "Body: ".length($body)." bytes\n"; $cv->send; },;$cv->recv;
$ perl http_request.pl200: OKBody: 10875 bytes
AnyEvent::Internals
AE::HTTP
sub http_request($$@) { my $cb = pop; my ($method,$url) = (uc shift, shift); my (%arg) = ( timeout => 10, recurse => 10, @_); my %state; my %hdr = (connection => 'close'); my $err = sub { %state = (); $cb->(undef, { Status => $_[1], Reason => $_[0] || 599, URL => $_[2] || $url } ); }; return $err->("Too many redirections") if $arg{recurse} < 0;
while (my ($k, $v) = each %{$arg{headers}}) { $hdr{lc $k} = $v };
$hdr{"user-agent"} //= "AnyEvent/$AnyEvent::VERSION";
$hdr{"content-length"} = length $arg{body} if length $arg{body} or $method ne "GET";
# ...}
AnyEvent::Internals
AE::HTTP
use AnyEvent::Socket;sub http_request($$@) { # ... my ($rscheme, $uauthority, $rpath, $query, $fragment) = $url =~ m{(?:([^:/?#]+):)?(?://([^/?#]*))? ([^?#]*)(?:\?([^#]*))?(?:#(.*))?}x; $rscheme = lc $rscheme;
my $rport = $rscheme eq "http" ? 80 : $rscheme eq "https" ? 443 : return $err->("Only http/https are supported");
$uauthority =~ /^(?: .*\@ )? ([^\@:]+) (?: : (\d+) )?$/x or return $err->("Unparsable URL"); my $rhost = $1; $rport = $2 if defined $2; $hdr{host} //= defined $2 ? "$uhost:$2" : "$uhost"; $rhost =~ s/^\[(.*)\]$/$1/; $rpath .= "?$query" if length $query; $rpath =~ s%^/?%/%;
$state{connect_guard} = tcp_connect $rhost, $rport, sub { # ... }, sub { $arg{timeout} }; defined wantarray && AnyEvent::Util::guard { %state = () };}
AnyEvent::Internals
AE::HTTP
use AnyEvent::Socket;
tcp_connect $host, $port, sub { # Connect sub my ($fh, $addr, $port) = @_; # ... }, sub { # Prepare sub my ($socket) = @_; return $timeout; };
AnyEvent::Internals
AE::HTTP
use AnyEvent::Handle;sub http_request($$@) { # ... $state{connect_guard} = tcp_connect $rhost, $rport, sub { $state{fh} = shift or return $err->("$!"); return unless delete $state{connect_guard};
$state{handle} = new AnyEvent::Handle fh => $state{fh}, timeout => $arg{timeout}, on_error => sub { $err->($_[2]) }, on_eof => sub { $err->("Unexpected end-of-file") }; $state{handle}->starttls ("connect") if $rscheme eq "https";
$state{handle}->push_write ( "$method $rpath HTTP/1.0\015\012". join( "", map { defined $hdr{$_} ? "\u$_: ".delete($hdr{$_})."\015\012" : delete $hdr{$_}||() } keys %hdr )."\015\012". delete($arg{body}) ); $state{handle}->push_read (line => qr{\015?\012}, sub { # ... }); }, sub { $arg{timeout} }; # ...}
AnyEvent::Internals
AE::HTTP
use AnyEvent::Handle;
$h->push_write($data);
$h->push_read( chunk => $bytes, sub { my ($h,$data) = @_; # ...});$h->push_read( line => qr/EOL/, sub { ... } );
$h->unshift_read( chunk => $bytes, sub { ... } );$h->unshift_read( line => qr/EOL/, sub { ... } );
$h->on_read( sub { ... } );
$h->{rbuf}
AnyEvent::Internals
AE::HTTP
sub http_request($$@) { # ... $state{handle}->push_read ( line => qr{\015?\012}, sub { $_[1] =~ /^HTTP\/([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\015\012]*) )?/ix or return $err->("Invalid server response ($_[1])");
my %hdr = ( Status => ",$2", Reason => ",$3", URL => ",$url" );
$_[0]->unshift_read ( line => qr{(? sub { # ... } ); } ); # ...}
AnyEvent::Internals
AE::HTTP
sub http_request($$@) { # ... $_[0]->unshift_read ( line => qr{(? sub { for ("$_[1]") { y/\015//d; $hdr{lc $1} .= ",$2" while / \G ([^:\000-\037]*): [\011\040]* (( ?: [^\012]+ | \012[\011\040] )*) \012 /gxc; /\G$/ or return $err->("Garbled response headers"); } substr $_, 0, 1, '' for values %hdr; if ($hdr{location} !~ /^(?: $ | [^:\/?\#]+ : )/x) { $hdr{location} =~ s/^\.\/+//; my $url = "$rscheme://$rhost:$rport"; unless ($hdr{location} =~ s{^/}{}) { $url .= $rpath; $url =~ s{/[^/]*$}{}; } $hdr{location} = "$url/$hdr{location}"; } # ... }); # ...}
AnyEvent::Internals
AE::HTTP
sub http_request($$@) { # ... my $redirect; if ($arg{recurse}) { if ( ($hdr{Status} =~ /^30[12]$/ and $method ne "POST" ) or ($hdr{Status} == 307 and $method =~ /^(?:GET|HEAD)$/) ) { $redirect = 1; } elsif ($hdr{Status} == 303) { $method = "GET" unless $method eq "HEAD"; $redirect = 1; } } my $finish = sub { $state{handle}->destroy if $state{handle}; %state = ();
if ($redirect && exists $hdr{location}) { http_request $method => $hdr{location}, %arg, recurse => $arg{recurse} - 1, $cb; } else { $cb->($_[0], $_[1]); } }; # ...}
AnyEvent::Internals
AE::HTTP
sub http_request($$@) { # ... my $len = $hdr{"content-length"}; if ( $hdr{Status} =~ /^(?:1..|[23]04)$/ or $method eq "HEAD" or (defined $len && !$len) ) { # no body $finish->("", \%hdr); } else { # ... } # ...}
AnyEvent::Internals
AE::HTTP
sub http_request($$@) { # ... $_[0]->on_eof (undef); if ($len) { # have content length $_[0]->on_error (sub { $finish->(undef,{ Status => 599, Reason => $_[2], URL => $url }); }); $_[0]->on_read (sub { $finish->( substr(delete $_[0]{rbuf}, 0, $len, ""), \%hdr ) if $len on_error (sub { $!{ EPIPE} || !$! ? $finish->(delete $_[0]{rbuf}, \%hdr) : $finish->(undef,{ Status => 599, Reason => $_[2], URL => $url }); }); $_[0]->on_read(sub {}); } # ...}
AnyEvent::Internals
AE::HTTP
use AnyEvent::Socket;use AnyEvent::Handle;sub http_request($$@) { my $cb = pop; my ($method,$url) = (uc shift, shift); my (%arg) = ( timeout => 10, recurse => 10, @_); my %state; my %hdr = (connection => 'close'); my $err = sub { %state = (); $cb->(undef, Status => $_[1], Reason => $_[0]||599, URL => $_[2]||$url ) }; return $err->("Too many redirections") if $arg{recurse} < 0;
while (my ($k, $v) = each %{$arg{headers}}) { $hdr{lc $k} = $v };
my ($rscheme, $uauthority, $rpath, $query, $fragment) = $url =~ m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|; $rscheme = lc $rscheme; my $rport = $rscheme eq "http" ? 80 : $rscheme eq "https" ? 443 : return $err->("Only http and https URL schemes supported"); $uauthority =~ /^(?: .*\@ )? ([^\@:]+) (?: : (\d+) )?$/x or return $err->("Unparsable URL"); my $rhost = $1; $rport = $2 if defined $2; $hdr{host} //= defined $2 ? "$rhost:$2" : "$rhost"; $rhost =~ s/^\[(.*)\]$/$1/; $rpath .= "?$query" if length $query; $rpath =~ s%^/?%/%;
$hdr{"user-agent"} //= "AnyEvent/$AnyEvent::VERSION"; $hdr{"content-length"} = length $arg{body} if length $arg{body} or $method ne "GET";
$state{connect_guard} = tcp_connect $rhost, $rport, sub { $state{fh} = shift or return %state=(),$err->("$!"); return unless delete $state{connect_guard};
$state{handle} = new AnyEvent::Handle fh => $state{fh}, timeout => $arg{timeout}, on_error => sub { $err->($_[2]) }, on_eof => sub { $err->("Unexpected end-of-file") }, ; $state{handle}->starttls ("connect") if $rscheme eq "https";
$state{handle}->push_write ( "$method $rpath HTTP/1.0\015\012". join( "", map { defined $hdr{$_} ? "\u$_: ".delete($hdr{$_})."\015\012" : delete $hdr{$_}||() } keys %hdr )."\015\012". delete($arg{body}) );
$state{handle}->push_read (line => qr{\015?\012}, sub { $_[1] =~ /^HTTP\/([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\015\012]*) )?/ix or return $err->("Invalid server response ($_[1])");
my %hdr = ( Status => ",$2", Reason => ",$3", URL => ",$url" );
$state{handle}->unshift_read (line => qr{(? for ("$_[1]") { y/\015//d; $hdr{lc $1} .= ",$2" while / \G ([^:\000-\037]*): [\011\040]* (( ?: [^\012]+ | \012[\011\040] )*) \012 /gxc; /\G$/ or return $err->("Garbled response headers"); } substr $_, 0, 1, '' for values %hdr;
if ($hdr{location} !~ /^(?: $ | [^:\/?\#]+ : )/x) { $hdr{location} =~ s/^\.\/+//; my $url = "$rscheme://$rhost:$rport"; unless ($hdr{location} =~ s{^/}{}) { $url .= $rpath; $url =~ s{/[^/]*$}{}; } $hdr{location} = "$url/$hdr{location}"; }
my $redirect; if ($arg{recurse}) { if ( ($hdr{Status} =~ /^30[12]$/ and $method ne "POST" ) or ($hdr{Status} == 307 and $method =~ /^(?:GET|HEAD)$/) ) { $redirect = 1; } elsif ($hdr{Status} == 303) { $method = "GET" unless $method eq "HEAD"; $redirect = 1; } }
my $finish = sub { $state{handle}->destroy if $state{handle}; %state = ();
if ($redirect && exists $hdr{location}) { http_request $method => $hdr{location}, %arg, recurse => $arg{recurse} - 1, $cb; } else { $cb->($_[0], $_[1]); } };
my $len = $hdr{"content-length"}; if ( $hdr{Status} =~ /^(?:1..|[23]04)$/ or $method eq "HEAD" or (defined $len && !$len) ) { # no body $finish->("", \%hdr); } else { $_[0]->on_eof (undef); if ($len) { # have content length $_[0]->on_error (sub { $finish->(undef, { Status => 599, Reason => $_[2], URL => $url }) }); $_[0]->on_read (sub {$finish->( substr(delete $_[0]{rbuf}, 0, $len, ""), \%hdr) if $len on_error (sub { $!{ EPIPE} || !$! ? $finish->(delete $_[0]{rbuf}, \%hdr) : $finish->(undef,{ Status => 599, Reason => $_[2], URL => $url }); }); $_[0]->on_read(sub {}); } } }); }); }, sub { $arg{timeout} }; defined wantarray && AnyEvent::Util::guard { %state = () }}
AnyEvent::Internals
AE::HTTPServer
my $server = AnyEvent::HTTP::Server->new( port => 80, request => sub { my $r = shift; if ($r->wants_websocket) { $r->upgrade('websocket', sub { if (my $ws = shift) { $ws->onmessage(sub { $ws->send("@_"); }); $ws->send("Hello!"); } else { warn "Upgrade failed: @_"; } }); } else { # ... } },);
AnyEvent::Internals
AE::HTTPServer
tcp_server $self->{host}, $self->{port}, sub { my $fh = shift or return warn "couldn't accept client: $!"; my ($host, $port) = @_; my $con = $self->{connection_class}->new( server => $self, fh => $fh, host => $host, port => $port, on_error => sub { my $con = shift; warn "@_"; delete $self->{con}{$con->{id}}; }, ); $self->{con}{$con->{id}} = $con;}, sub { 1024;};
AnyEvent::Internals
AE::HTTPServer
sub Connection::new { my $self = bless {}, shift; weaken (my $this = $self); my %args = @_; my $srv = $args{server}; my $fh = $args{fh}; my $h = AnyEvent::Handle::Writer->new( fh => $args{fh}, on_eof => sub { $this or return; $this->error("EOF") }, on_error => sub { $this or return; $this->error("$!") }, ); weaken($self->{srv} = $args{server}); $self->{id} = int $self; $self->{fh} = $args{fh}; $self->{h} = $h; $self->{host} = $args{host}; $self->{port} = $args{port}; $self->{r} = []; # Request queue $self->{ka_timeout} = $srv->{keep_alive_timeout} || 30; if ($srv->{keep_alive}) { $self->{touch} = AE::now; $self->ka_timer; } $self->read_header(); return $self;}
AnyEvent::Internals
AE::HTTPServer
sub Connection::ka_timer { my $self = shift; $self->{srv} or return $self->destroy; weaken (my $this = $self); $self->{ka} = AE::timer $this->{ka_timeout} + 1, 0, sub { $this or return; if (AE::now - $this->{touch} >= $this->{ka_timeout}) { $this->close; } else { $this->ka_timer; } } ;}
AnyEvent::Internals
AE::HTTPServer
sub Connection::read_header { my $self = shift; $self->{srv} or return $self->destroy; weaken (my $con = $self); $con->{h}->push_read(chunk => 3 => sub { $con or return; shift; my $pre = shift; if ($pre =~ m{^{h}->unshift_read(regex => qr{.+?>} => sub { my $xml = $pre.shift(); # Hanlde XML here $con->destroy(); }); } else { $con->{h}->unshift_read(line => sub { $con or return;shift; my $line = $pre.shift; if ($line =~ /(\S+) \040 (\S+) \040 HTTP\/(\d+\.\d+)/xso) { my ($meth, $url, $hv) = ($1, $2, $3); $con->{method} = $meth; $con->{uri} = $url; $con->{version} = $hv; $con->read_headers(); } elsif ($line eq '') { $con->read_header(); } else { $con->fatal_error(400); } }); }; });}
AnyEvent::Internals
AE::HTTPServer
sub Connection::read_headers { my ($self) = @_; $self->{srv} or return $self->destroy; weaken (my $con = $self); $self->{h}->unshift_read( line => qr{(? sub { $con or return; my ($h, $data) = @_; my $hdr = HTTP::Easy::Headers->decode($data) // return $con->fatal_error(400); my $r = $con->{srv}{request_class}->new( method => delete $con->{method}, uri => delete $con->{uri}, host => $hdr->{Host}, headers => $hdr, ); push @{ $con->{r} }, $r; weaken($r->{con} = $con); weaken($con->{r}[-1]); # ... } );}
AnyEvent::Internals
AE::HTTPServer
sub { $con or return; # ... $hdr->{connection} = lc $hdr->{connection}; if ($hdr->{connection} eq 'close' or $con->{version} < 1.1) { delete $con->{ka}; $con->{type} = 'close'; $con->{close} = 1; } elsif ($hdr->{connection} =~ /keep-alive/) { $con->{close} = 0; $con->{type} = 'keep-alive'; } elsif ($hdr->{connection} eq 'upgrade') { delete $con->{ka}; $con->{type} = 'upgrade'; $con->{close} = 0; } if (defined $hdr->{'content-length'}) { $con->{h}->unshift_read( chunk => $hdr->{'content-length'}, sub { my ($hdl, $data) = @_; $con->handle_request($r, $data); $con->read_header() if $con->{ka}; }); } else { $con->handle_request($r); $con->read_header() if $con->{ka}; } }
AnyEvent::Internals
AE::HTTPServer
sub Request::response { my $self = shift; $self->{con} or return $self->dispose; $self->{con}->response($self, @_); $self->dispose;}
sub Request::dispose { my $self = shift; return %$self = ();}
sub Request::DESTROY { my $self = shift; $self->{con} or return %$self = (); $self->{con}->response( $self, 404, '', msg => "Request not handled" );}
sub Request::wants_websocket { my $self = shift; return lc $self->{headers}{connection} eq 'upgrade' && lc $self->{headers}{upgrade} eq 'websocket' ? 1 : 0;}
AnyEvent::Internals
AE::HTTPServer
sub Request::upgrade { my $self = shift; my $cb = pop; my $type = shift; my $headers = shift || HTTP::Easy::Headers->new({}); if (lc $type eq 'websocket') { $headers->{upgrade} = 'WebSocket'; $headers->{connection} = 'Upgrade'; $headers->{'websocket-origin'} ||= $self->{headers}{origin}; $headers->{'websocket-location'} ||= do { my $loc = URI->new_abs($self->{uri}, "http://$self->{headers}{host}"); $loc = "$loc"; $loc =~ s{^http}{ws}; $loc; }; $self->{con}->response($self,101,'',headers => $headers, msg => "Web Socket Protocol Handshake"); my $ws = $self->{con}{srv}{websocket_class}->new( con => $self->{con}, ); $self->dispose; return $cb->($ws); } else { return $cb->(undef, "Unsupported upgrade type: $type"); }}
AnyEvent::Internals
AE::HTTPServer
sub Connection::response { my ($con,$r,$code,$content,%args) = @_; my $msg = $args{msg} || $HTTP::Easy::Status::MSG{$code}; my $hdr = $args{headers} || HTTP::Easy::Headers->new({});
# Resolve pipeline if (@{$con->{r}} and $con->{r}[0] == $r) { shift @{ $con->{r} }; } else { $r->{ready} = [ $code, $msg, $hdr, $content ]; return; } my $res = "HTTP/$con->{version} $code $msg\015\012"; $hdr->{'content-type'} ||= 'text/html'; if (ref $content eq 'HASH') { if ($content->{sendfile}) { $content->{size} = $hdr->{'content-length'} = -s $content->{sendfile}; } } $hdr->{connection} ||= $con->{type}; if ($code >= 400 and !length $content ) { $content = "$code $msg"; } # ...
AnyEvent::Internals
AE::HTTPServer
# ... $hdr->{'content-length'} = length $content if not (defined $hdr->{'content-length'}) and not ref $content and $code !~ /^(?:1\d\d|[23]04)$/;
$res .= $hdr->encode(); $con->{h}->push_write($res);
if (ref $content eq 'HASH') { if ($content->{sendfile}) { $con->{h}->push_sendfile( $content->{sendfile}, $content->{size}); } else { die "Bad response content"; } } else { $con->{h}->push_write($content) if length $content; }
if ($con->{close}) { $con->close(); } elsif ( @{$con->{r}} and $con->{r}[0]{ready}) { $con->response($con->{r}[0],@{$con->{r}[0]{ready}}); }}
AnyEvent::Internals
AE::Memcached
my $cv = AE::cv;$cv->begin;$cv->begin(sub { $cv->send });
my $memd = AnyEvent::Memcached->new( servers => [ '127.0.0.1:11211' ], cv => $cv, namespace => "test:",);
$memd->set("key1", "val1", cb => sub { shift or return warn "Set key1 failed: @_"; $memd->get("key1", cb => sub { my ($v,$e) = @_; $e and return warn "Get failed: $e"; warn "Got value for key1: $v"; });});
$cv->end;$cv->recv;
AnyEvent::Internals
AE::Memcached
sub set { shift->_set( set => @_) }
sub _set { my $self,$cmd,$key) = splice @_,0,3; my $cas; if ($cmd eq 'cas') { $cas = shift; } my $val = shift; # Some preparations... $self->_do( $key, "$cmd $self->{namespace}%s $flags $expire $len". ( defined $cas ? ' '.$cas : '')."\015\012$val", sub { local $_ = shift; if ($_ eq 'STORED') { return 1 } elsif ($_ eq 'NOT_STORED') { return 0 } elsif ($_ eq 'EXISTS') { return 0 } else { return undef, $_ } }, cb => $args{cb}, );}
AnyEvent::Internals
AE::Memcached
sub _do { my ($self,$key,$com,$wrk,%args) = @_; my $servers = $self->{hash}->servers($key); my %res; my %err; my $res; $_ and $_->begin for $self->{cv}, $args{cv}; my $cv = AE::cv { if ($res != -1) { $args{cb}($res); } else { $args{cb}( undef, dumper(\%err) ); } $_ and $_->end for $args{cv}, $self->{cv}; }; for my $srv ( keys %$servers ) { for my $real (@{ $servers->{$srv} }) { $cv->begin; my $cmd = $com; substr($cmd, index($cmd,'%s'),2) = $real; $self->{peers}{$srv}{con}->command( '...' ); } } return;}
AnyEvent::Internals
AE::Memcached
# ... $self->{peers}{$srv}{con}->command( $cmd, cb => sub { if (defined( local $_ = shift )) { my ($ok,$fail) = $wrk->($_); if (defined $ok) { $res{$real}{$srv} = $ok; $res = (!defined $res) || $res == $ok ? $ok : -1; } else { $err{$real}{$srv} = $fail; $res = -1; } } else { $err{$real}{$srv} = $_; $res = -1; } $cv->end; } );# ...
AnyEvent::Internals
AE::Memcached
sub Peer::command { my $self = shift; if ($self->{connected}) { return $self->{con}->command( @_ ); } else { my ($cmd,%args) = @_; $self->conntrack( command => \@_, $args{cb} ); }}
AnyEvent::Internals
AE::Memcached
sub Peer::conntrack { my $self = shift; my ($method,$args,$cb) = @_; if($self->{connecting} and $self->{failed}) { $cb and $cb->(undef, "Not connected"); return; } elsif (!$self->{connected}) { # connect } else { return $self->{con}->$method(@$args); }}
AnyEvent::Internals
AE::Memcached
sub Peer::connect { my $self = shift; $self->{connecting} and return; $self->reg_cb( connected => sub { $self->{failed} = 0; } ); $self->reg_cb( connfail => sub { $self->{failed} = 1; } ); $self->reg_cb( disconnect => sub { shift;shift; %$self or return; my $e = @_ ? "@_" : "disconnected"; for ( keys %{$self->{waitingcb}} ) { if ($self->{waitingcb}{$_}) { $self->{waitingcb}{$_}(undef,$e); } delete $self->{waitingcb}{$_}; } } ); $self->next::method(@_); return;}
AnyEvent::Internals
AE::Memcached
elsif (!$self->{connected}) { my @args = @$args; # copy to avoid rewriting my ($c,$t); weaken( $self ); weaken( $self->{waitingcb}{int $cb} = $cb ) if $cb; $c = $self->reg_cb( connected => sub { shift->unreg_me; undef $c; undef $t; $self or return; delete $self->{waitingcb}{int $cb} if $cb; return $self->{con}->$method(@args); }); $t = AE::timer $self->{timeout}, 0, sub { undef $c;undef $t; $self or return; if ($cb){ delete $self->{waitingcb}{int $cb}; $cb->(undef, "Connect timeout"); } }, ); $self->connect(); }
AnyEvent::Internals
Resources
Documentation AE
AnyEvent
AnyEvent::Intro
Authors
Marc Lehmann (MLEHMANN)
Robin Redeker (ELMEX)
Mons Anderson (MONS)
AnyEvent::Internals
JFDI
Use Perl or die;
AnyEvent::Internals
Author
Mons Anderson(aka )Rambler Internet Holding
DevConf::Perl()2010
$cv->send;