EV-Gearman
view release on metacpan or search on metacpan
The handle binds the per-job event callbacks to the right submission
even when many submissions are in flight at once.
Worker lifecycle:
CAN_DO(func) --> # advertise
repeat ----------------------+
GRAB_JOB --> |
<-- JOB_ASSIGN(h,fn,wl) | # got work
... user callback |
WORK_COMPLETE(h, r) --> |
-------------- or -----------+
<-- NO_JOB |
PRE_SLEEP --> |
<-- NOOP | # wake-up
----------------------------+
`EV::Gearman` drives that state machine in C; the per-function
callback only sees a ready-to-process [EV::Gearman::Job](https://metacpan.org/pod/EV%3A%3AGearman%3A%3AJob).
# ENCODING
All function names, payloads, results, and handles are byte strings
on the wire. Encode UTF-8 yourself before passing data in:
use Encode;
$cli->submit_job(reverse => encode_utf8($str), sub {
my $result = decode_utf8($_[0] // '');
...
});
Workload and result values can contain arbitrary bytes including
embedded NULs â Gearman's framing puts the payload last in the
packet and uses the header's length field, so it is not NUL-bounded.
# CALLBACK CONVENTIONS
Every command callback receives `($result, $err)`. On success
`$err` is `undef`; on failure `$err` is a string like
`"disconnected"`, `"job failed"`, `"command timeout"`, or text
forwarded from the server (e.g. `"INVALID_FUNCTION_NAME: ..."`).
Callback exceptions are caught with `G_EVAL` and surfaced via
`warn` so a stray `die` from your code never unwinds the libev
event loop. Use `EV::break` to abort the loop deliberately.
# CONSTRUCTOR
## new(%options)
my $g = EV::Gearman->new(
host => '127.0.0.1',
port => 4730,
on_error => sub { warn "@_" },
on_connect => sub { ... },
on_disconnect => sub { ... },
connect_timeout => 5_000, # ms
command_timeout => 30_000, # ms
reconnect => 1,
reconnect_delay => 1000, # ms
keepalive => 60, # seconds (TCP only)
exceptions => 1, # request "exceptions" option
client_id => "worker-$$",
grab_unique => 1, # use GRAB_JOB_UNIQ
);
If `host` (or `path`) is given, a non-blocking connect starts
immediately. With neither, the object is unconfigured; call
`$g->connect` / `$g->connect_unix` later.
All keys default to `undef` unless noted. Booleans accept any Perl
truthy value.
### Connection
- `host => $str`
- `port => $int`
TCP host and port. Default port: `4730`. Mutually exclusive with
`path`.
Name resolution is currently synchronous: a non-numeric `host` is
passed straight to `getaddrinfo`, which can block the event loop
for the system resolver timeout. Pass an IP literal (or pre-resolve
once) to keep reconnect cycles fully non-blocking.
- `path => $str`
Unix-domain socket path. Mutually exclusive with `host`.
Note that a `connect(2)` to a unix socket completes inline, so the
connection is fully established (and `on_connect` has already fired)
before `new` returns â a `$g->on_connect(...)` assigned after
construction will never fire for it. Pass `on_connect` to the
constructor instead (the same can theoretically happen for a TCP
connect that completes immediately).
- `loop => $ev_loop`
EV loop to attach to. Default: `EV::default_loop`.
- `priority => $num`
EV watcher priority in `-2 .. +2`. Higher = serviced before other
EV watchers in the same iteration. Default `0`.
- `keepalive => $seconds`
TCP keepalive idle interval. `0` disables. Ignored on Unix sockets.
### Timeouts
- `connect_timeout => $ms`
Abort an in-progress non-blocking connect after this many ms. `0`
= no timeout (default).
- `command_timeout => $ms`
Per-request timeout. The request at the head of the pending queue is
given this many ms from the moment it is written to the socket; if it
is still unanswered when its budget expires, the connection is torn
down with `"command timeout"`. The budget is independent of
unrelated traffic: other packets arriving meanwhile neither extend
the head request's budget nor shorten it, and once the head is
answered the next request's own budget applies. A slow reply that
keeps dribbling in is therefore safe as long as the request completes
within its budget â and a genuinely stuck request dies on schedule
even on an otherwise busy connection. `0` = no timeout (default).
### Reconnect
- `reconnect => $bool`
Enable automatic reconnect on transport errors.
- `reconnect_delay => $ms`
Wait this many ms before each reconnect attempt. Default `1000`.
The delay is always honored via a timer, so even `0` defers
through the event loop (no synchronous retry recursion).
- `max_reconnect_attempts => $num`
Give up after this many consecutive failures and emit
`"max reconnect attempts reached"`. `0` = unlimited (default).
After a reconnect, all worker `CAN_DO`/`CAN_DO_TIMEOUT`
registrations and the `exceptions` option are re-sent
automatically.
### Worker / option flags
- `exceptions => $bool`
If true, the `exceptions` option is sent on every connect, so
foreground clients receive `WORK_EXCEPTION` packets. For workers,
this also enables forwarding `die` messages from sync callbacks
as exceptions instead of the `WORK_FAIL` (`WORK_EXCEPTION` is
terminal at the server; sending both would earn a `JOB_NOT_FOUND`).
- `client_id => $str`
Sent as `SET_CLIENT_ID` on every connect. Visible in the admin
`workers` output.
- `grab_unique => $bool`
If true, the worker GRAB loop uses `GRAB_JOB_UNIQ`, so the job
gearmand answers a single-line `ERR` instead); everything else is
treated as single-line.
A single-line reply beginning with `ERR ` (gearmand's text-protocol
error prefix, e.g. for an unrecognized command) is delivered as the
error argument: `$cb->(undef, "ERR UNKNOWN_COMMAND ...")` â not
as a successful result. The connection stays up.
If a command not in the multi-line set above nevertheless gets a
`".\n"`-terminated multi-line reply from the server, only the first
line is delivered; the leftover lines cannot be attributed to any
request and the connection is torn down with a protocol error.
Because the text protocol has no length prefix, a buffered reply is
capped at 16 MiB: a peer that keeps streaming without ever sending
the terminator is dropped with an `"admin response too large"`
error (and the normal reconnect logic applies).
## server\_status(\[$cb\])
Tab-separated lines: `FUNC \t TOTAL_JOBS \t RUNNING_JOBS \t WORKERS`.
## server\_workers(\[$cb\])
One line per connected worker.
## server\_version(\[$cb\])
Single-line reply (e.g. `"OK 1.1.21+ds"`).
## maxqueue($func, $size, \[$cb\])
Set the per-function queue size cap. Reply is `"OK\n"`.
# INTROSPECTION
## pending\_count
Number of requests sent and awaiting a response â binary client
requests, admin (text-protocol) commands, and the worker's in-flight
`GRAB_JOB` all count.
## waiting\_count
Number of requests held in the local pre-connect queue.
## active\_count
Number of foreground jobs whose handle has been received but
which haven't yet completed.
# ACCESSORS
These tunables have a getter / setter of the same name. Calling
without arguments reads the current value; with one argument, writes
and (where meaningful) takes effect immediately:
$g->connect_timeout($ms);
$g->command_timeout($ms);
$g->priority($num);
$g->keepalive($seconds);
$g->on_error($cb); # set; pass undef to clear
$g->on_connect($cb);
$g->on_disconnect($cb);
The remaining `new` options (`host`, `port`, `path`,
`exceptions`, `client_id`, `grab_unique`, ...) are set once at
construction and have no accessor.
`reconnect` is the exception â it is a setter only; pass `0`/`1`
plus optional new delay and attempt cap:
$g->reconnect($enable, [$delay_ms], [$max_attempts]);
Omitting `$delay_ms` / `$max_attempts` leaves the previously
configured values unchanged.
## reconnect\_enabled
The getter that `reconnect` lacks: returns true while automatic
reconnect is enabled, false otherwise. Takes no arguments.
# LIFECYCLE AND DESTRUCTION
When a connection drops, the FIFO of pending requests is drained
with `(undef, "disconnected")`; foreground active jobs are drained
with the same error. Reconnect (if enabled) re-runs the connect
sequence and re-registers worker abilities.
When the `EV::Gearman` object goes out of scope, every pending
and active callback fires once with `(undef, "disconnected")`,
then the FD is closed. The clean-shutdown idiom is:
$g->disconnect; # drains queues, fires on_disconnect
undef $g;
If callbacks close over `$g` (a common mistake â every reference
inside a closure keeps the object alive), break the cycle first:
$g->on_error(undef);
$g->on_connect(undef);
$g->on_disconnect(undef);
undef $g;
DESTROY is reentrancy-safe: if a callback fired during teardown
drops the last external reference to a separate `EV::Gearman`,
that object's DESTROY is correctly deferred and run once unwound.
# PERFORMANCE
Loopback benchmark on Linux, Perl 5.40, gearmand 1.1.21, single
worker (always [EV::Gearman](https://metacpan.org/pod/EV%3A%3AGearman) so the worker isn't the bottleneck).
`bench/benchmark.pl` measures one client by itself:
ops/sec
Pipelined foreground jobs ~53,000
Sequential round-trip ~19,000
Background submissions ~280,000
`bench/vs.pl` compares against the existing CPAN clients
([Gearman::Client](https://metacpan.org/pod/Gearman%3A%3AClient) 2.004.015 sync, [AnyEvent::Gearman](https://metacpan.org/pod/AnyEvent%3A%3AGearman) 0.10
async). Numbers in operations / second:
EV::Gearman AnyEvent::Gearman Gearman::Client
pipelined foreground ~51,000 ~5,200 n/a (1)
sequential round-trip ~19,000 ~5,900 ~5,400
background submits ~248,000 ~5,400 n/a (1)
(1) Gearman::Client is synchronous â it has neither pipelining
nor concurrent background submits.
EV::Gearman is roughly **10x** the foreground throughput of
AnyEvent::Gearman, **45x** the background submission rate, and
**3x** the sequential round-trip rate. The gap comes from three
places:
- Pipelining is the default. Submitting N jobs in a tight loop
ships them in batched writes; responses are demultiplexed by
handle as they stream back. AnyEvent::Gearman is async but
serializes one request per round-trip, so it pays a full RTT per
job.
- The protocol implementation is C/XS â packet encode/decode,
buffer growth, and FIFO bookkeeping run without per-call Perl
allocations.
- The IO layer is direct `ev_io` on the gearmand socket, so each
read/write involves no AnyEvent guard-object construction or
backend-dispatch overhead.
Background submissions are particularly fast because the
JOB\_CREATED reply is the only round-trip â no work events to
demultiplex â so the limit is just network latency and parser
throughput.
Sequential round-trip throughput is the worst case: each job
waits for its own reply before the next is built, so pipelining
buys nothing. EV::Gearman is still ~3x faster here purely from
the C-side protocol parser.
( run in 1.258 second using v1.01-cache-2.11-cpan-14f38c9f855 )