IPC-Manager
view release on metacpan or search on metacpan
lib/IPC/Manager/Test.pm view on Meta::CPAN
# gone, and await_response would croak with "peer ... went away"
# instead of returning the response that had already been delivered.
#
# Reproduce the race deterministically: send the request via the
# async API, sleep long enough for the service to respond and fully
# tear down (so its registration is gone from the bus), then call
# await_response. The response is sitting in the parent's inbox;
# peer_active reports gone. Without the in-transit drain in
# await_response, this throws "peer ... went away".
my $iters = 5;
my @errors;
for my $i (1 .. $iters) {
my $done = 0;
my $name = "exit_after_resp_$i";
my $handle = ipcm_service(
$name,
class => 'IPC::Manager::Service',
handle_request => sub {
my ($self, $req, $msg) = @_;
$done = 1;
return {ok => 1, n => $req->{request}->{n}};
},
should_end => sub { $done },
);
my $req_id = $handle->send_request($name, {n => $i});
# Wait for the service process to die and the bus to deregister it.
# waitpid the child the spawn started, so peer_active() definitively
# reports gone by the time await_response runs. Prefer blocking
# waitpid for direct children; fall back to a short poll otherwise.
my $svc_pid = $handle->child_pid;
if (defined $svc_pid) {
my $r = waitpid($svc_pid, 0);
if ($r == -1) {
for (1 .. 50) {
last unless pid_is_running($svc_pid);
Time::HiRes::sleep(0.05);
}
}
}
else {
Time::HiRes::sleep(1);
}
my $resp;
my $ok = eval { $resp = $handle->await_response($req_id); 1 };
my $err = $@;
if (!$ok) {
push @errors => "iter $i: $err";
}
elsif (!ref($resp) || ($resp->{response}{n} // -1) != $i) {
push @errors => "iter $i: bad response: " . (ref($resp) ? $resp->{response}{n} // 'undef' : $resp);
}
$handle = undef;
}
is(\@errors, [], "no peer-gone exceptions across $iters exits-after-response iterations");
}
sub test_multiple_requests {
my $handle = ipcm_service(
'multi_svc',
class => 'IPC::Manager::Service',
handle_request => sub {
my ($self, $req, $msg) = @_;
return "reply_$req->{request}";
},
);
my @got;
for my $i (1 .. 5) {
$handle->send_request(
multi_svc => "req_$i",
sub {
my ($resp, $msg) = @_;
push @got, $resp->{response};
},
);
}
$handle->await_all_responses;
is(scalar @got, 5, "Got all 5 responses");
is([sort @got], [sort map { "reply_req_$_" } 1..5], "All responses correct");
$handle = undef;
}
sub test_handle_messages_buffer {
my $handle = ipcm_service(
'buf_svc',
class => 'IPC::Manager::Service',
handle_request => sub {
my ($self, $req, $msg) = @_;
# Send a non-response message back alongside the response
$self->client->send_message(
$msg->from,
{notification => "event_$req->{request}"},
);
return "ack";
},
);
my $resp = $handle->sync_request(buf_svc => "ping");
is($resp->{response}, "ack", "Got response");
# The notification should be in the handle's message buffer
my @buffered = $handle->messages;
is(scalar @buffered, 1, "One buffered non-response message");
is($buffered[0]->content->{notification}, "event_ping", "Notification content correct");
# Buffer is drained
my @again = $handle->messages;
is(scalar @again, 0, "Buffer empty after drain");
( run in 0.611 second using v1.01-cache-2.11-cpan-71847e10f99 )