NAME
UniEvent - Object-oriented, fast and extendable event loop abstraction framework with Perl and C++ interface.
SYNOPSIS
use UniEvent;
my $timer = UniEvent::Timer->create($interval, sub { ... });
my $signal = UniEvent::Signal->create(SIGHUP, sub {...});
my $client = UniEvent::Tcp->new;
$client->use_ssl();
$client->connect($host, $port);
$client->write($data);
$client->read_callback(sub {
my ($self, $data, $err) = @_;
...
});
my $server = UniEvent::Tcp->new;
$server->use_ssl;
$server->bind($host, $port);
$server->listen;
$server->connection_callback(sub {
my ($self, $client, $err) = @_;
$client->write("hello");
$client->read_callback(sub { ... });
$client->eof_callback(sub { ... });
});
Fs::stat($path, sub { ... });
UniEvent::Loop->default->run;
DESCRIPTION
UniEvent is an extendable object-oriented event loop framework. It's also an abstraction layer on top of event loop which provides engine-independent API (like AnyEvent).
This module is an XS port of the very fast C++ panda::unievent framework.
The main feature is that it supports implementing third-party plugins (protocol adapters) in C++/XS (see XS::Manifesto) and therefore without any perfomance penalties. UniEvent support multiple backends (libuv is the only implemented at the moment).
LOOP
The heart of event programming is an event loop object. This object runs the loop and polls for all registered events (handles).
You can create as many loops as you wish but you can only run one loop at a time. Each loop only polls for events registred with this loop.
my $loop = UE::Loop->new;
my $timer = UE::timer 10, sub {...}, $loop;
$loop->run; # timer will fire every 10 seconds
By default, one loop (which is called main or default) is automatically created for you and is accessible as
UE::Loop->default;
All event handles (watchers) will register in default loop if you don't specify a loop in their constructor.
my $timer = UE::timer 10, sub {...}; # registered in default loop
UE::Loop->default->run;
When you run
$loop->run;
The execution flow will not return until there are no more active handles in the loop or loop stop() is called from some callback.
$loop->stop;
Each handle has a strong refence to it's event loop so that loop will not be destroyed until you loose all refs to it and all it's handles are destroyed. The default loop is never destroyed.
HANDLES (WATCHERS)
There are a number of different handle classes to watch for different events like timers, signals, tcp and udp handles, filesystem operations, and so on. You can find detailed description of each handle class in reference.
Each handle object binds to a specific loop upon creation. Re-binding a handle to a different loop object after creation is not supported. A handle will watch for events as soon as you run the loop it was bound to.
Timer
Timer handle invokes callback periodically at some interval (may be fractional).
See UniEvent::Timer for details.
Signal
Signal handle watches for signal events.
my $signal = UE::signal SIGINT(), sub {
finish_stuff();
exit 1;
};
You can get signal constant from UniEvent::Signal::SIGxxx()
or from any perl module that provides it.
Signal will not interrupt your program and the callback is invoked as soon as possible when it's safe to do so.
UniEvent replaces signal watcher in libc
so you MUST NOT mix it with perl's %SIG callbacks for the same signal numbers.
See UniEvent::Signal for details.
Idle
Idle handle invokes callback when the loop is idle, i.e. there are no new events after polling for i/o, timer, etc. On each loop iteration.
Normally, loop polls for events with some suitable timeout. If an idle handle is started, the timeout is 0. This means that your process will consume all available CPU resources until idle handle is stopped.
This is useful when you want to do some expensive work but you don't want to block process and you want to continue processing new events as they occur. IN this case you can setup and idle handle and do some small chunk of work on each callback invocation and then remove the idle handle when you're done.
my $idle = UE::idle sub {
if (my $job = @stuff_to_do) {
# do the job
} else {
$idle = undef;
}
};
See UniEvent::Idle for details.
Prepare/Check
Prepare
handles will run the given callback once per loop iteration, right before polling for i/o.
Check
handles will run the given callback once per loop iteration, right after polling for i/o.
See UniEvent::Prepare and UniEvent::Check for details.
TCP
TCP handles are used to represent both TCP streams and servers.
my $tcp = UE::Tcp->new;
$tcp->connect($host, $port); # async resolve and connect
$tcp->write("data"); # will write when connected
$tcp->read_callback(sub {
my ($tcp, $data, $err) = @_;
...
});
...
$tcp->disconnect;
$tcp = UE::Tcp->new;
$tcp->bind($host, $port);
$tcp->listen;
$tcp->connection_callback(sub {
my ($server, $client, $err) = @_;
$client->read_callback(sub { ... });
...
});
See UniEvent::Tcp for details.
Pipe
Pipe handles provide an abstraction over streaming files on Unix (including local domain sockets, pipes, and FIFOs) and named pipes on Windows.
See UniEvent::Pipe for details.
TTY
TTY handles represent a stream for the console.
See UniEvent::Tty for details.
UDP
UDP handles encapsulate UDP communication for both clients and servers.
See UniEvent::Udp for details.
I/O poll
Poll handles are used to watch file descriptors for readability, writability and disconnection.
NOTE: If you want to connect, write, and read from network connections or make a network server, you'd better use more convenient and more efficient cross-platform tcp
, udp
, pipe
, etc... handles. Also those handles make use of fast I/O completion ports (IOCP) on Windows which is not possible with pure I/O poll handle.
The purpose of poll handles is to enable integrating external libraries that rely on the event loop to signal it about the socket status changes.
my $h = UE::poll $filehande_or_fileno, UE::Poll::READABLE | UE::Poll::WRITABLE, sub {
my ($handle, $events, $err) = @_;
...
};
Filesystem events
FS Event handles allow the user to monitor a given path for changes, for example, if the file was renamed or there was a generic change in it. This handle uses the best backend for the job on each platform.
my $handle = UE::fs_event $path, UE::FsEvent::CHANGE, sub {
my ($handle, $path, $events, $err) = @_;
# file $path changed
};
See UniEvent::FsEvent for details.
Filesystem poll
FS Poll handles allow the user to monitor a given path for changes. Unlike FsEvent
, fs poll handles use stat
to detect when a file has changed so they can work on file systems where fs event handles can’t.
my $handle = UE::fs_poll $path, $interval, sub {
my ($handle, $prev_stat, $cur_stat, $err) = @_;
...
};
See UniEvent::FsPoll for details.
HOLDING HANDLES
UniEvent does not hold a strong reference for created handle objects. You must hold them by yourself otherwise no events will be watched for.
UE::timer 1, sub {...}; # timer is destroyed immediately as you didn't hold it
UE::Loop->default->run; # no events to watch, run() will return immediately
Usually you have an object to place a refence to handle to. However sometimes it is convenient to capture handle in callback. Keep in mind that if you do this
my $timer = UE::Timer->new($loop);
$timer->start(1, sub {
...
if (...) { $timer->stop }
});
yes, you will hold the timer, however you will create a cyclic reference timer -> callback -> timer and thus create a memory leak. This code will stop the timer but will never destroy it. To remove cyclic reference, you need to break the cycle
my $timer = UE::Timer->new($loop);
$timer->start(1, sub {
...
if (...) {
$timer->stop;
$timer = undef; # release captured variable
}
});
or
my $timer = UE::Timer->new($loop);
$timer->start(1, sub {
...
if (...) {
$timer->stop;
$timer->event->remove_all; # remove all callbacks
}
});
EVENT CALLBACKS
Each handle provides several methods for setting event callbacks.
The naming rule for such methods is usually as follows:
If a handle provides only single type of event (like UniEvent::Timer, UniEvent::Idle, etc...), then these methods are named
callback()
andevent()
.$timer->callback(sub { ... }); $timer->event->add(sub { ... });
If a handle provides several types of events (like any of UniEvent::Stream's ancestors, UniEvent::Udp, etc...), then these methods are named
xxxx_callback()
andxxxx_event()
, whenxxxx
is an event name.$tcp->read_callback(sub { ... }); $tcp->write_event->add(sub { ... });
A handle can hold any number of callbacks for each event type. A callback will receive certain parameters which is documented in each handle's docs. The first parameter is always the handle object it is set to.
Calling event()
method returns XS::Framework::CallbackDispatcher object which is a special container for callbacks. To add several callbacks, simply call add()
method several times on that object.
$timer->event->add(sub { ... });
$timer->event->add(sub { ... });
The callbacks call order by default is in order of adding, i.e. the first added callback is called the first. To add callback in front prepend()
can be used, see XS::Framework::CallbackDispatcher docs.
To remove a certain callback, call remove()
with corresponding subroutine reference.
my $cb = sub { ... };
$timer->event->add($cb);
$timer->event->remove($cb);
An anonymous callback can remove itself like this:
$timer->event->add(sub {
my $timer = shift;
if (...) {
$timer->event->remove(__SUB__);
}
});
To remove all callbacks from an event object, call remove_all()
.
$timer->event->add(sub { ... });
$timer->event->add(sub { ... });
$timer->event->remove_all; # removes both callbacks
Method callback()
is an efficient shortcut which sets a single callback removing any previously set callbacks if any
$timer->callback(sub { ... }); # same as $timer->event->remove_all() + $timer->event->add(sub { ... })
EVENT LISTENER
Instead of setting callbacks for each event type
$tcp->read_callback(sub { ... });
$tcp->write_callback(sub { ... });
you can set an event listener object which can watch for all events types.
package MyListener;
sub on_read { ... }
sub on_write { ... }
sub on_shutdown { ... }
sub on_disconnect { ... }
...
$tcp->event_listener(MyListener->new);
Event listener can be object of any type. If event listener is set, a handle will call methods on_xxxx()
on it, where xxxx
is an event name. The parameters are the same as for callback version (first param is the event listener object itself, of course, second is the handle object, and other params are according to event documentation).
An event listener object can be set to multiple handles.
package MyListener;
sub on_timer { ... }
sub on_check { ... }
...
my $lst = MyListener->new;
$timer->event_listener($lst);
$check->event_listener($lst);
Event listener can't be set via simple API UE::check($callback, $loop)
. Only subroutines are accepted there.
Event listener does not disable callbacks, i.e. if both event listener object and callbacks are set to a handle, both will be called (first callbacks, then event listener's method).
Event listener is convenient when some object makes use of several handles and wants to listen events from them. Then instead of setting many callbacks it can set itself as a listener for those handles. This saves cpu time and can make the code clearer.
package MyClass;
sub new {
my $self = bless {}, shift;
$self->{timer} = UE::Timer->new;
$self->{timer}->start(10);
$self->{timer}->event_listener($self, 1);
$self->{tcp} = UE::Tcp->new;
$self->{tcp}->connect($host, $port);
$self->{tcp}->event_listener($self, 1);
}
sub on_timer {
my ($self, $timer) = @_;
...
}
sub on_connect {
my ($self, $tcp, $err) = @_;
...
}
Second argument to event_listener
is an weak
flag. If set to true
, handle will hold an event listener object by a weak reference. In our example, setting this flag is mandatory, because otherwise a memory leak would occur due to indirect cyclic reference $self -> timer/tcp handle -> $self (event listener)
In the previous examples with MyListener
class, weak
flag should NOT be set as we need strong reference there, otherwise, when local variable $lst
goes out of scope, event listener object will be destroyed and removed from all handles it was set to.
ASYNC DNS
UniEvent supports for trully asyncronous resolve (async DNS). You may use it indirectly, for example via $tcp->connect($host, $port) method or directly like this:
my $resolver = UniEvent::Resolver->new($loop);
$resolver->resolve({
node => 'myhost.com',
use_cache => 1,
hints => {family => UniEvent::AF_INET},
on_resolve => sub {
my ($addr, $err) = @_;
say $addr->[0]->ip;
},
});
$loop->run;
For more details, see UniEvent::Resolver, UniEvent::Tcp.
FILESYSTEM ASYNC OPERATIONS
UniEvent provides a wide variety of cross-platform sync and async file system operations. For example:
UE::Fs::stat($file, sub {
my ($stat, $err) = @_;
...
});
UE::Fs::mkstemp($template, sub {
my ($path, $fd, $err) = @_;
...
});
UE::Loop->default->run;
See UniEvent::Fs for details.
UTILITY FUNCTIONS
UniEvent provides a number of cross-platform utility functions described in FUNCTIONS
section of UniEvent package. Almost all of them are syncronous and not related to an event loop.
my $info = UniEvent::cpu_info();
my $num_cpus = @$info;
for (1..$num_cpus) {
...
fork();
...
}
See "FUNCTIONS" in UniEvent section.
OPTIONAL ERRORS
Some functions and methods in UniEvent uses optional error return. Such functions instead of throwing an exception (if any error occurs) can return that error if asked to do so.
You can choose desired behaviour by calling those function in certain context.
There are two types of function / methods returning optional exceptions:
- functions with result
-
If you call such a function normally, in scalar context, it will throw an exception if anything goes wrong, for example.
my $sockaddr = $tcp->sockaddr; # either return a sockaddr or throw an exception
It's okay if you don't bother about possible errors and agree to die if an error occurs. However if you desire to catch errors and process them, then exceptions are not always a convenient way and definitely not efficient. It could be more desirable to get the error directly as return value. To do so simply call such function in two-value context and it will return a possible error as the second value.
my ($sockaddr, $error) = $tcp->sockaddr; # never throws
In this case if no errors occur,
$error
will beundef
. Otherwise$sockaddr
will be undef and$error
will be an error object XS::ErrorCode or XS::STL::ErrorCode. You can inspect that object to find out what exactly happened.Calling such function in void context is the same as calling in scalar context (it will throw an exception in case of error).
- functions with no result (void)
-
If you call such a function normally, in void context, it will throw an exception if anything goes wrong, for example.
$tcp->open($socket); # either succeed or throws
If called in scalar context, such function will return just an "ok" flag and will not throw an exception.
my $ok = $tcp->open($socket); # never throws unless ($tcp->open($socket)) { # never throws # failover somehow }
If called in two-value context it will return an "ok" flag and an error if any.
my ($ok, $error) = $tcp->open($socket); # never throws say $error if $error;
Such functions are marked in documentation as May return error
NOTE: async functions and methods doesn't use this approach, they always pass errors as an argument to async callback and never throws.
FORK
UniEvent is completely fork-aware. It automatically does all the stuff needed after fork. You can just fork() and run any loop after that including the ones that were in use in the master process.
However keep in mind, that if you run the same loop in child process that was running in master process, you should probably remove the master's process event handles to avoid double execution. This is especially important for I/O handles like tcp, udp, pipe and so on, because no usable behaviour will occur if you watch for the same descriptior with 2 or more different handles.
That's why it is often more convenient to run different loop in child process than to remove all master's recources from the same loop.
# master
my $loop = UE::Loop->new;
my $timer = UE::timer 1, sub {...}, $loop;
my $tcp = UE::Tcp->new();
...
fork();
#child
my $child_loop = UE::Loop->new;
...add events
$child_loop->run;
THREADS
UniEvent supports perl threads, however perl threads support is highly experimental and requires all XS modules in stack to support them. Threads behaviour in perl and C/C++ is completely different so it requires a huge amount of magic for XS module which ports a complex C++ framework to work.
UniEvent depends on a number of XS modules and a number of XS modules depend on UniEvent. If there will be a single perl-threads-unaware thing, the app may crash at some point.
Event loops and handles are NOT thread-safe, you can't run or access the same loop/handles in different threads. The only exception is UniEvent::Async handle which is thread-safe and designed for inter-thread communication.
In threaded perl UniEvent::Loop->default is thread-local (different in each thread), so you can safely create handles and run default loop in each thread.
For safety, UniEvent objects are non-clonable, i.e. after creating a thread, all UniEvent objects (like loop, handles, requests, etc) that existed before threading will become undef
in child thread.
NOTE: using threaded perl and NOT creating a second thread is fully supported and operational.
SHORT API
UE
is an alias for UniEvent
. You can use it whereever you would use UniEvent
.
my $t = UniEvent::Timer->new;
my $t = UE::Timer->new; # the same
However keep in mind, that created objects are always of UniEvent::XXXX
class. It matters in these kind of checks:
if (ref($t) eq 'UniEvent::Timer') # ok
if (ref($t) eq 'UE::Timer') # WRONG!
FUNCTIONS
check($callback, [$loop = default])
fs_event($path, $flags, $callback, [$loop = default])
fs_poll($path, $interval, $callback, [$loop = default])
idle($callback, [$loop = default])
poll($fd, $events, $callback, [$loop = default])
prepare($callback, [$loop = default])
signal($signum, $callback, [$loop = default])
signal_once($signum, $callback, [$loop = default])
timer($repeat, $callback, [$loop = default])
timer_once($initial, $callback, [$loop = default])
A shortcuts for corresponding UniEvent::XXXXX->create(...) call.
my $timer = UniEvent::timer($repeat, $cb);
my $timer = UniEvent::Timer->create($repeat, $cb); # the same
See corresponding package's docs for more info.
hostname()
Returns current machine host name
uname()
Retrieves system information. Data includes the operating system name, release, version, and machine.
{
machine => 'x86_64'
release => '4.19.0-14-amd64'
sysname => 'Linux'
version => '#1 SMP Debian 4.19.171-2 (2021-01-30)'
}
get_rss()
Gets the resident set size (RSS) for the current process (in bytes).
get_free_memory()
Gets the amount of free memory available in the system, as reported by the kernel (in bytes).
get_total_memory()
Gets the total amount of physical memory in the system (in bytes).
interface_info()
Gets address information about the network interfaces on the system. Sample output:
[{
address => 127.0.0.1:0, # Net::SockAddr
is_internal => 1,
name => 'lo',
netmask => 255.0.0.0:0, # Net::SockAddr
phys_addr => "\0\0\0\0\0\0"
},
{
address => 10.10.10.92:0, # Net::SockAddr
is_internal => 0,
name => 'wlp2s0',
netmask => 255.255.254.0:0, # Net::SockAddr
phys_addr => "\274\250\246\203S\$"
}
]
...
cpu_info()
Gets information about the CPUs on the system. Sample output:
[{
cpu_times => {
idle => 168327400,
irq => 655200,
nice => 1495000,
sys => 3045400,
user => 11124000
},
model => 'Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz',
speed => 800
},
{
cpu_times => {
idle => 168750000,
irq => 598300,
nice => 1300800,
sys => 2522600,
user => 11583100
},
model => 'Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz',
speed => 800
}
];
get_rusage()
Gets the resource usage measures for the current process. Some platforms might not have all fields. Sample output:
{
idrss => 0, # integral unshared data size
inblock => 0, # block input operations
isrss => 0, # integral unshared stack size
ixrss => 0, # integral shared memory size
majflt => 0, # page faults (hard page faults)
maxrss => 50512, # maximum resident set size
minflt => 9810, # page reclaims (soft page faults)
msgrcv => 0, # IPC messages received
msgsnd => 0, # IPC messages sent
nivcsw => 0, # involuntary context switches
nsignals => 0, # signals received
nswap => 0, # swaps
nvcsw => 54, # voluntary context switches
oublock => 0, # block output operations
stime => 0.032753, # user CPU time used
utime => 0.356583 # system CPU time used
};
get_random($size)
Returns string filled with $size
trully random bytes. May block until needed amount of bytes become available.
get_random($size, $callback, [$loop = default])
Async version of get_random()
.
Callback will be called with 3 arguments: resulting string, XS::STL::ErrorCode error object if any and request object.
Returns UniEvent::Request::Random object.
get_random(1000, sub {
my ($data, $err) = @_;
});
UE::Loop->default->run;
default_backend()
Returns current default UniEvent backend.
Default backend is used when you create a UniEvent::Loop object without specifying backend.
By default, default backend is UniEvent::Backend::UV
.
set_default_backend($backend)
Sets default UniEvent backend.
It can only be set very early (usually on program startup), when default loop is not yet created (not accessed). This method will throw an exception if it's too late for changing default backend.
CONSTANTS
There are many constants in UniEvent
framework. Most of them are defined and documented in packages they are used with.
Here is the list of constants that are defined in main UniEvent
package because they are used with multiple packages.
AF_INET
AF_INET6
AF_UNSPEC
PF_INET
PF_INET6
PF_UNSPEC
SOCK_STREAM
SOCK_DGRAM
LOGS
Logs are accessible via XLog framework as "UniEvent" module.
XLog::set_logger(XLog::Console->new);
XLog::set_level(XLog::DEBUG, "UniEvent");
References
UniEvent::Streamer::FileOutput
UniEvent::Streamer::StreamInput
UniEvent::Streamer::StreamOutput
SEE ALSO
AUTHOR
Pronin Oleg <syber@cpan.org>
Grigory Smorkalov <g.smorkalov@crazypanda.ru>
Ivan Baidakou <i.baydakov@crazypanda.ru>
Crazy Panda LTD
LICENSE
You may distribute this code under the same terms as Perl itself.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 231:
Non-ASCII character seen before =encoding in 'can’t.'. Assuming UTF-8