PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Gearman-Client-Async-0.94/lib/Gearman/Client/Async/Connection.pm

#
Perl | 414 lines | 304 code | 99 blank | 11 comment | 45 complexity | 2be800a7ab828cdeee0c86abeb1a45c6 MD5 | raw file
  1. package Gearman::Client::Async::Connection;
  2. use strict;
  3. use warnings;
  4. use Danga::Socket;
  5. use base 'Danga::Socket';
  6. use fields (
  7. 'state', # one of 3 state constants below
  8. 'waiting', # hashref of $handle -> [ Task+ ]
  9. 'need_handle', # arrayref of Gearman::Task objects which
  10. # have been submitted but need handles.
  11. 'parser', # parser object
  12. 'hostspec', # scalar: "host:ip"
  13. 'deadtime', # unixtime we're marked dead until.
  14. 'task2handle', # hashref of stringified Task -> scalar handle
  15. 'on_ready', # arrayref of on_ready callbacks to run on connect success
  16. 'on_error', # arrayref of on_error callbacks to run on connect failure
  17. 't_offline', # bool: fake being off the net for purposes of connecting, to force timeout
  18. );
  19. our $T_ON_TIMEOUT;
  20. use constant S_DISCONNECTED => \ "disconnected";
  21. use constant S_CONNECTING => \ "connecting";
  22. use constant S_READY => \ "ready";
  23. use Carp qw(croak);
  24. use Gearman::Task;
  25. use Gearman::Util;
  26. use Scalar::Util qw(weaken);
  27. use IO::Handle;
  28. use Socket qw(PF_INET IPPROTO_TCP TCP_NODELAY SOL_SOCKET SOCK_STREAM);
  29. sub DEBUGGING () { 0 }
  30. sub new {
  31. my Gearman::Client::Async::Connection $self = shift;
  32. my %opts = @_;
  33. $self = fields::new( $self ) unless ref $self;
  34. my $hostspec = delete( $opts{hostspec} ) or
  35. croak("hostspec required");
  36. if (ref $hostspec eq 'GLOB') {
  37. # In this case we have been passed a globref, hopefully a socket that has already
  38. # been connected to the Gearman server in some way.
  39. $self->SUPER::new($hostspec);
  40. $self->{state} = S_CONNECTING;
  41. $self->{parser} = Gearman::ResponseParser::Async->new( $self );
  42. $self->watch_write(1);
  43. } elsif (ref $hostspec && $hostspec->can("to_inprocess_server")) {
  44. # In this case we have been passed an object that looks like a Gearman::Server,
  45. # which we can just call "to_inprocess_server" on to get a socketpair connecting
  46. # to it.
  47. my $sock = $hostspec->to_inprocess_server;
  48. $self->SUPER::new($sock);
  49. $self->{state} = S_CONNECTING;
  50. $self->{parser} = Gearman::ResponseParser::Async->new( $self );
  51. $self->watch_write(1);
  52. }else {
  53. $self->{state} = S_DISCONNECTED;
  54. }
  55. $self->{hostspec} = $hostspec;
  56. $self->{waiting} = {};
  57. $self->{need_handle} = [];
  58. $self->{deadtime} = 0;
  59. $self->{on_ready} = [];
  60. $self->{on_error} = [];
  61. $self->{task2handle} = {};
  62. croak "Unknown parameters: " . join(", ", keys %opts) if %opts;
  63. return $self;
  64. }
  65. sub close_when_finished {
  66. my Gearman::Client::Async::Connection $self = shift;
  67. # FIXME: implement
  68. }
  69. sub hostspec {
  70. my Gearman::Client::Async::Connection $self = shift;
  71. return $self->{hostspec};
  72. }
  73. sub connect {
  74. my Gearman::Client::Async::Connection $self = shift;
  75. $self->{state} = S_CONNECTING;
  76. my ($host, $port) = split /:/, $self->{hostspec};
  77. $port ||= 7003;
  78. warn "Connecting to $self->{hostspec}\n" if DEBUGGING;
  79. socket my $sock, PF_INET, SOCK_STREAM, IPPROTO_TCP;
  80. IO::Handle::blocking($sock, 0);
  81. setsockopt($sock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die;
  82. unless ($sock && defined fileno($sock)) {
  83. warn( "Error creating socket: $!\n" );
  84. return undef;
  85. }
  86. $self->SUPER::new( $sock );
  87. $self->{parser} = Gearman::ResponseParser::Async->new( $self );
  88. eval {
  89. connect $sock, Socket::sockaddr_in($port, Socket::inet_aton($host));
  90. };
  91. if ($@) {
  92. $self->on_connect_error;
  93. return;
  94. }
  95. Danga::Socket->AddTimer(0.25, sub {
  96. return unless $self->{state} == S_CONNECTING;
  97. $T_ON_TIMEOUT->() if $T_ON_TIMEOUT;
  98. $self->on_connect_error;
  99. });
  100. # unless we're faking being offline for the test suite, connect and watch
  101. # for writabilty so we know the connect worked...
  102. unless ($self->{t_offline}) {
  103. $self->watch_write(1);
  104. }
  105. }
  106. sub event_write {
  107. my Gearman::Client::Async::Connection $self = shift;
  108. if ($self->{state} == S_CONNECTING) {
  109. $self->{state} = S_READY;
  110. $self->watch_read(1);
  111. warn "$self->{hostspec} connected and ready.\n" if DEBUGGING;
  112. $_->() foreach @{$self->{on_ready}};
  113. $self->destroy_callbacks;
  114. }
  115. $self->watch_write(0) if $self->write(undef);
  116. }
  117. sub destroy_callbacks {
  118. my Gearman::Client::Async::Connection $self = shift;
  119. $self->{on_ready} = [];
  120. $self->{on_error} = [];
  121. }
  122. sub event_read {
  123. my Gearman::Client::Async::Connection $self = shift;
  124. my $input = $self->read( 128 * 1024 );
  125. unless (defined $input) {
  126. $self->mark_dead if $self->stuff_outstanding;
  127. $self->close( "EOF" );
  128. return;
  129. }
  130. $self->{parser}->parse_data( $input );
  131. }
  132. sub event_err {
  133. my Gearman::Client::Async::Connection $self = shift;
  134. my $was_connecting = ($self->{state} == S_CONNECTING);
  135. if ($was_connecting && $self->{t_offline}) {
  136. $self->SUPER::close( "error" );
  137. return;
  138. }
  139. $self->mark_dead;
  140. $self->close( "error" );
  141. $self->on_connect_error if $was_connecting;
  142. }
  143. sub on_connect_error {
  144. my Gearman::Client::Async::Connection $self = shift;
  145. warn "Jobserver, $self->{hostspec} ($self) has failed to connect properly\n" if DEBUGGING;
  146. $self->mark_dead;
  147. $self->close( "error" );
  148. $_->() foreach @{$self->{on_error}};
  149. $self->destroy_callbacks;
  150. }
  151. sub close {
  152. my Gearman::Client::Async::Connection $self = shift;
  153. my $reason = shift;
  154. if ($self->{state} != S_DISCONNECTED) {
  155. $self->{state} = S_DISCONNECTED;
  156. $self->SUPER::close( $reason );
  157. }
  158. $self->_requeue_all;
  159. }
  160. sub mark_dead {
  161. my Gearman::Client::Async::Connection $self = shift;
  162. $self->{deadtime} = time + 10;
  163. warn "$self->{hostspec} marked dead for a bit." if DEBUGGING;
  164. }
  165. sub alive {
  166. my Gearman::Client::Async::Connection $self = shift;
  167. return $self->{deadtime} <= time;
  168. }
  169. sub add_task {
  170. my Gearman::Client::Async::Connection $self = shift;
  171. my Gearman::Task $task = shift;
  172. Carp::confess("add_task called when in wrong state")
  173. unless $self->{state} == S_READY;
  174. warn "writing task $task to $self->{hostspec}\n" if DEBUGGING;
  175. $self->write( $task->pack_submit_packet );
  176. push @{$self->{need_handle}}, $task;
  177. Scalar::Util::weaken($self->{need_handle}->[-1]);
  178. }
  179. sub stuff_outstanding {
  180. my Gearman::Client::Async::Connection $self = shift;
  181. return
  182. @{$self->{need_handle}} ||
  183. %{$self->{waiting}};
  184. }
  185. sub _requeue_all {
  186. my Gearman::Client::Async::Connection $self = shift;
  187. my $need_handle = $self->{need_handle};
  188. my $waiting = $self->{waiting};
  189. $self->{need_handle} = [];
  190. $self->{waiting} = {};
  191. while (@$need_handle) {
  192. my $task = shift @$need_handle;
  193. warn "Task $task in need_handle queue during socket error, queueing for redispatch\n" if DEBUGGING;
  194. $task->fail if $task;
  195. }
  196. while (my ($shandle, $tasklist) = each( %$waiting )) {
  197. foreach my $task (@$tasklist) {
  198. warn "Task $task ($shandle) in waiting queue during socket error, queueing for redispatch\n" if DEBUGGING;
  199. $task->fail;
  200. }
  201. }
  202. }
  203. sub process_packet {
  204. my Gearman::Client::Async::Connection $self = shift;
  205. my $res = shift;
  206. warn "Got packet '$res->{type}' from $self->{hostspec}\n" if DEBUGGING;
  207. if ($res->{type} eq "job_created") {
  208. die "Um, got an unexpected job_created notification" unless @{ $self->{need_handle} };
  209. my Gearman::Task $task = shift @{ $self->{need_handle} } or
  210. return 1;
  211. my $shandle = ${ $res->{'blobref'} };
  212. if ($task) {
  213. $self->{task2handle}{"$task"} = $shandle;
  214. push @{ $self->{waiting}->{$shandle} ||= [] }, $task;
  215. }
  216. return 1;
  217. }
  218. if ($res->{type} eq "work_fail") {
  219. my $shandle = ${ $res->{'blobref'} };
  220. $self->_fail_jshandle($shandle);
  221. return 1;
  222. }
  223. if ($res->{type} eq "work_complete") {
  224. ${ $res->{'blobref'} } =~ s/^(.+?)\0//
  225. or die "Bogus work_complete from server";
  226. my $shandle = $1;
  227. my $task_list = $self->{waiting}{$shandle} or
  228. return;
  229. my Gearman::Task $task = shift @$task_list or
  230. return;
  231. $task->complete($res->{'blobref'});
  232. unless (@$task_list) {
  233. delete $self->{waiting}{$shandle};
  234. delete $self->{task2handle}{"$task"};
  235. }
  236. warn "Jobs: " . scalar( keys( %{$self->{waiting}} ) ) . "\n" if DEBUGGING;
  237. return 1;
  238. }
  239. if ($res->{type} eq "work_status") {
  240. my ($shandle, $nu, $de) = split(/\0/, ${ $res->{'blobref'} });
  241. my $task_list = $self->{waiting}{$shandle} or
  242. return;
  243. foreach my Gearman::Task $task (@$task_list) {
  244. $task->status($nu, $de);
  245. }
  246. return 1;
  247. }
  248. die "Unknown/unimplemented packet type: $res->{type}";
  249. }
  250. sub give_up_on {
  251. my Gearman::Client::Async::Connection $self = shift;
  252. my $task = shift;
  253. my $shandle = $self->{task2handle}{"$task"} or return;
  254. my $task_list = $self->{waiting}{$shandle} or return;
  255. @$task_list = grep { $_ != $task } @$task_list;
  256. unless (@$task_list) {
  257. delete $self->{waiting}{$shandle};
  258. }
  259. }
  260. # note the failure of a task given by its jobserver-specific handle
  261. sub _fail_jshandle {
  262. my Gearman::Client::Async::Connection $self = shift;
  263. my $shandle = shift;
  264. my $task_list = $self->{waiting}->{$shandle} or
  265. return;
  266. my Gearman::Task $task = shift @$task_list or
  267. return;
  268. # cleanup
  269. unless (@$task_list) {
  270. delete $self->{task2handle}{"$task"};
  271. delete $self->{waiting}{$shandle};
  272. }
  273. $task->fail;
  274. }
  275. sub get_in_ready_state {
  276. my ($self, $on_ready, $on_error) = @_;
  277. if ($self->{state} == S_READY) {
  278. $on_ready->();
  279. return;
  280. }
  281. push @{$self->{on_ready}}, $on_ready if $on_ready;
  282. push @{$self->{on_error}}, $on_error if $on_error;
  283. $self->connect if $self->{state} == S_DISCONNECTED;
  284. }
  285. sub t_set_offline {
  286. my ($self, $val) = @_;
  287. $val = 1 unless defined $val;
  288. $self->{t_offline} = $val;
  289. }
  290. package Gearman::ResponseParser::Async;
  291. use strict;
  292. use warnings;
  293. use Scalar::Util qw(weaken);
  294. use Gearman::ResponseParser;
  295. use base 'Gearman::ResponseParser';
  296. sub new {
  297. my $class = shift;
  298. my $self = $class->SUPER::new;
  299. $self->{_conn} = shift;
  300. weaken($self->{_conn});
  301. return $self;
  302. }
  303. sub on_packet {
  304. my $self = shift;
  305. my $packet = shift;
  306. return unless $self->{_conn};
  307. $self->{_conn}->process_packet( $packet );
  308. }
  309. sub on_error {
  310. my $self = shift;
  311. return unless $self->{_conn};
  312. $self->{_conn}->mark_unsafe;
  313. $self->{_conn}->close;
  314. }
  315. 1;