EV-Kafka
view release on metacpan or search on metacpan
lib/EV/Kafka.pm view on Meta::CPAN
if ($cfg->{sasl}) {
$conn->sasl($cfg->{sasl}{mechanism}, $cfg->{sasl}{username}, $cfg->{sasl}{password});
}
$self->_wire_conn_handlers($conn);
}
# Error/disconnect reporting for client-managed conns. An
# auto_reconnect conn reports its loss ONCE via on_disconnect; reconnect
# attempts stay silent so on_error (default: die) doesn't storm.
sub _wire_conn_handlers {
my ($self, $conn) = @_;
my $cfg = $self->{cfg};
$cfg->{_conn_state}{"$conn"} //= { auto_reconnect => 0, was_ready => 0 };
weaken(my $weak_cfg = $cfg);
$conn->on_error(sub {
my $cfg = $weak_cfg or return;
return if $cfg->{closed};
my $st = $cfg->{_conn_state}{"$conn"} // {};
return if $st->{auto_reconnect} && !$conn->connected;
$cfg->{on_error}->($_[0]) if $cfg->{on_error};
});
$conn->on_disconnect(sub {
my $cfg = $weak_cfg or return;
return if $cfg->{closed};
my $st = $cfg->{_conn_state}{"$conn"} // {};
return unless $st->{was_ready}; # never came up: nothing lost
$st->{was_ready} = 0; # report once per loss
$cfg->{on_error}->("connection to broker lost; reconnecting")
if $cfg->{on_error};
});
}
sub _bootstrap_connect {
my ($self) = @_;
$self->_bootstrap_try(0);
}
sub _bootstrap_try {
my ($self, $idx) = @_;
my $cfg = $self->{cfg};
return if $cfg->{closed};
my @bs = @{$cfg->{bootstrap}};
if ($idx >= @bs) {
$cfg->{_bootstrap_attempt} = undef;
# Connect callbacks are queued (not captured) so close() can fail
# them and nothing retains them past the first fire.
my @cbs = @{delete $cfg->{_connect_cbs} // []};
if (@cbs) {
$_->(undef, "all bootstrap brokers unreachable") for @cbs;
} elsif ($cfg->{on_error}) {
$cfg->{on_error}->("all bootstrap brokers unreachable");
}
return;
}
my ($host, $port) = @{$bs[$idx]};
my $conn = EV::Kafka::Conn::_new('EV::Kafka::Conn', $self->{loop});
$self->_configure_conn($conn);
# Keeps the attempt conn alive until on_connect/on_error fires,
# without a closure cycle.
$cfg->{_bootstrap_attempt} = $conn;
weaken(my $weak = $self);
# Replace the forwarding on_error from _configure_conn with the
# try-next-broker handler for the duration of the bootstrap.
$conn->on_error(sub {
my $s = $weak or return;
my $cfg = $s->{cfg};
return if $cfg->{closed};
$cfg->{_bootstrap_attempt} = undef;
$s->_bootstrap_try($idx + 1);
});
# State-driven: this handler outlives the bootstrap (XS refires
# on_connect after auto-reconnect), so it must not refire the cb.
$conn->on_connect(sub {
my $s = $weak or return;
my $cfg = $s->{cfg};
return if $cfg->{closed};
if (!$cfg->{connected}) {
# first successful bootstrap: promote attempt to bootstrap_conn
my $conn = delete $cfg->{_bootstrap_attempt} or return;
$cfg->{bootstrap_conn} = $conn;
$cfg->{connected} = 1;
# Now a pooled conn: auto-reconnect on loss, and the standard
# suppressing handlers replace the try-next-broker one.
$conn->auto_reconnect(1, 1000);
$cfg->{_conn_state}{"$conn"} = { auto_reconnect => 1, was_ready => 1 };
$s->_wire_conn_handlers($conn);
$s->_refresh_metadata;
} else {
# auto-reconnect: refresh metadata, do NOT refire the callback
$s->_refresh_metadata unless $cfg->{meta_pending};
}
});
$conn->connect($host, $port, 10.0);
}
sub connect {
my ($self, $cb) = @_;
my $cfg = $self->{cfg};
croak "EV::Kafka: client is closed" if $cfg->{closed};
# Already up: fire immediately. Otherwise queue; every connect cb
# fires exactly once â ($meta) on success, (undef, $err) on failure.
if ($cfg->{connected} && $cfg->{meta}) {
$cb->($cfg->{meta}) if $cb;
return;
}
push @{$cfg->{_connect_cbs}}, $cb if $cb;
$self->_bootstrap_connect
unless $cfg->{_bootstrap_attempt} || $cfg->{meta_pending};
}
sub _merge_metadata {
my ($cfg, $meta) = @_;
for my $b (@{$meta->{brokers} // []}) {
$cfg->{broker_map}{$b->{node_id}} = {
host => $b->{host}, port => $b->{port}
lib/EV/Kafka.pm view on Meta::CPAN
Enable automatic reconnection. C<$delay_ms> (default 1000) is the base
delay: the first retry happens after exactly C<$delay_ms>, later retries
use capped exponential backoff (doubling each attempt, up to 30s, with
+/-25% jitter), resetting once a connection succeeds. Reconnects reuse
the timeout from the original C<connect()> call.
=head2 leave_group($group_id, $member_id, $cb)
Send LeaveGroup to coordinator for fast partition rebalance.
=head2 create_topics(\@topics, $timeout_ms, $cb)
Create topics. Each element: C<{name, num_partitions, replication_factor}>.
$conn->create_topics(
[{ name => 'new-topic', num_partitions => 3, replication_factor => 1 }],
5000, sub { my ($res, $err) = @_ }
);
=head2 delete_topics(\@topic_names, $timeout_ms, $cb)
Delete topics by name.
=head2 init_producer_id($transactional_id, $txn_timeout_ms, $cb)
Initialize a producer ID for idempotent/transactional produce.
Pass C<undef> for non-transactional idempotent producer.
=head2 add_partitions_to_txn($txn_id, $producer_id, $epoch, \@topics, $cb)
Register partitions with the transaction coordinator.
=head2 end_txn($txn_id, $producer_id, $epoch, $committed, $cb)
Commit (C<$committed=1>) or abort (C<$committed=0>) a transaction.
=head2 txn_offset_commit($txn_id, $group_id, $producer_id, $epoch, $generation, $member_id, \@offsets, $cb)
Commit consumer offsets within a transaction (API 28).
=head2 pending
Number of requests awaiting broker response.
=head2 state
Connection state as integer (0=disconnected, 6=ready).
=head2 Object lifetime
The connection is torn down when its last reference drops, or when
C<DESTROY> is called explicitly; explicit destruction is idempotent.
Pending request callbacks are invoked with a C<'destroyed'> error during
teardown. Once destroyed, the object is inert: every subsequent method
call on it (from any copy of the reference) croaks with
C<EV::Kafka::Conn: method called on destroyed connection> -- including
calls made from callbacks running during the teardown itself (those
exceptions are caught by the callback dispatcher and reported via
C<warn()>). A conn passed a custom C<EV::Loop> holds a reference on it,
so the loop cannot be destroyed while the conn is alive.
=head1 UTILITY FUNCTIONS
=head2 EV::Kafka::_murmur2($key)
Kafka-compatible murmur2 hash. Returns a non-negative 31-bit integer.
=head2 EV::Kafka::_crc32c($data)
CRC32C checksum (Castagnoli). Used internally for RecordBatch integrity.
=head2 EV::Kafka::_error_name($code)
Convert Kafka error code to string name.
=head1 RESULT STRUCTURES
=head2 Produce result
$result = {
topics => [{
topic => 'name',
partitions => [{
partition => 0,
error_code => 0,
base_offset => 42,
}],
}],
};
=head2 Fetch result
$result = {
topics => [{
topic => 'name',
partitions => [{
partition => 0,
error_code => 0,
high_watermark => 100,
records => [{
offset => 42,
timestamp => 1712345678000,
key => 'key', # or undef
value => 'value', # or undef
headers => { h => 'v' }, # if present
}],
}],
}],
};
=head2 Metadata result
$result = {
controller_id => 0,
brokers => [{ node_id => 0, host => '10.0.0.1', port => 9092 }],
topics => [{
name => 'topic',
error_code => 0,
partitions => [{
partition => 0,
( run in 2.665 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )