POE::Queue(3pm)

πŸ’– POE::Queue πŸ’–

okay bestie, gather round, because we're about to talk about POE::Queue β€” a flexible, generic priority queue API, living its best life in the User Contributed Perl Documentation section of the manual pages. yes it's a 3pm page. yes it's iconic. let's GO πŸ¦‹


✨ SYNOPSIS ✨

so POE::Queue also specifies some extra methods that are not illustrated down here, bestie, keep that in mind. but here's the vibe:

#!perl

use warnings;
use strict;
use POE::Queue::Array;

my $pqa = POE::Queue::Array->new();

# Enqueue a few items.

foreach my $priority (505, 404, 303, 202, 101) {
  $pqa->enqueue($priority, "payload $priority");
}

# Dequeue until the queue is drained.

while (1) {
  my ($priority, $queue_id, $payload) = $pqa->dequeue_next();
  last unless defined $priority;

  print(
    "dequeued id($queue_id) ",
    "priority($priority) ",
    "payload($payload)\n",
  );
}

and the sample output, which honestly ate:

dequeued id(5) priority(101) payload(payload 101)
dequeued id(4) priority(202) payload(payload 202)
dequeued id(3) priority(303) payload(payload 303)
dequeued id(2) priority(404) payload(payload 404)
dequeued id(1) priority(505) payload(payload 505)

see how the priorities come out low-to-high but the ids come out high-to-low? that's not a glitch, that's the whole point. πŸ’…


🌸 DESCRIPTION 🌸

okay so, priority queues? you can build them like a million different ways, but they all kinda act like lists that are kept in order by some kind of "priority". the way it works is: enqueued items get stored so that the "next" item you pull out is the one with the highest priority. then the next fetch gives you the next lowest priority, and so on and so on, until the queue is completely empty. no crumbs left. πŸ“

priority queues (also known as priority heaps, iykyk) are trying to do all of this while using the fewest resources possible. go read about it!! it's genuinely fascinating stuff, no cap!! πŸ“šβœ¨

πŸŽ€ POE::Queue Items πŸŽ€

every POE::Queue item is made of exactly three fields, periodt:

  • a priority
  • a unique ID that gets assigned at enqueue time
  • a payload

the priority and the payload? those come from you, the caller. the unique ID? that's generated by POE::Queue itself when an item gets enqueued. she does that. πŸ‘‘

POE::Queue puts exactly two limitations on priorities, and that's it:

  1. priorities must be numeric
  2. lower numbers indicate higher priorities

beyond that? POE::Queue literally does not care what the numbers mean. they could mean seconds, vibes, whatever. not her business. πŸ™ˆ

Unique IDs are the handles into the queue. POE::Queue generates them and hands them back to you as new items are enqueued. some methods manipulate items, and those methods take IDs so they know which item to alter.

Item payloads are arbitrary application data. POE::Queue does not examine or alter payloads herself. any method that needs to peek at a payload will accept a filter function instead β€” filter functions examine the payloads so POE::Queue doesn't have to. delegation, queen behavior, we love to see it. 🫢

Public Methods

POE::Queue is an API specification. subclasses like POE::Queue::Array are the ones that provide actual implementations. think of POE::Queue as the blueprint and the subclass as the girl who actually shows up and does the work. 😭

new

creates a new priority queue. returns a reference to the queue. that's it, that's the whole job.

my $queue = POE::Queue::Array->new();

enqueue PRIORITY, PAYLOAD

enqueues a PAYLOAD β€” and bestie, that payload can be just about anything that'll fit into a Perl scalar β€” at a particular PRIORITY level. enqueue() returns a unique ID which you can use to manipulate the payload or its priority directly later. very "here's your receipt, keep it safe" of her. 🧾

following the UNIX tradition, lower priority numbers indicate higher priorities. the payload with the lowest priority number gets dequeued first. and if two payloads have the same PRIORITY? then they get dequeued in the order they were enqueued. first come, first served, no cutting the line. βœ‹

in this example a queue is used to manage a bunch of alarms, and the "next" alarm is the one due soonest:

my $payload_id = $queue->enqueue($alarm_time, [ "stuff" ]);

dequeue_next

removes the next item from the queue and returns it as three fields: priority, ID and payload.

the "next" item is the one with the lowest priority number. if multiple items exist with the same priority, dequeue_next() returns whichever one was given that priority first.

ITEM: while (1) {
  my ($priority, $id, $payload) = $queue->dequeue_next();
  last ITEM unless defined $priority;
  ...;
}

get_next_priority

returns the priority of the item at the head of the queue. that's the lowest numeric priority in the whole queue. πŸ‘€

get_next_priority() is lowkey so useful for checking the queue to see if it's time to dequeue some items yet. in this case the queue manages multiple alarms, and there's literally nothing to do if the next alarm isn't due yet. so she just... waits. iconic behavior honestly.

ALARM: while (1) {
  my $next_alarm_time = $queue->get_next_priority();
  last ALARM unless defined $next_alarm_time;

  if ($next_alarm_time - time() > 0) {
    sleep($next_alarm_time - time());
  }

  my ($priority, $id, $payload) = $queue->dequeue_next();
  ...;
}

get_item_count

returns the number of items in the queue. it's another way to tell whether the queue has been fully drained. here's an alternative version of the example from get_next_priority():

ALARM: while ($queue->get_item_count()) {
  my $next_alarm_time = $queue->get_next_priority();
  if ($next_alarm_time - time() > 0) {
    sleep($next_alarm_time - time());
  }

  my ($priority, $id, $payload) = $queue->dequeue_next();
  ...;
}

remove_item ITEM_ID, FILTER_FUNCTION

removes a single item by its ID β€” but only if a FILTER_FUNCTION approves of the item's payload first. consent matters!! 🫢

if a payload is found with the given ITEM_ID, it gets passed to FILTER_FUNCTION for examination. if FILTER_FUNCTION returns true, the item is removed from the queue and returned as three fields.

my ($priority, $id, $payload) = $queue->remove_item(
  $target_id, \&monkeys
);

sub monkeys {
  my $payload = shift;
  $payload->{type} eq "monkey";
}

the returned $priority will be undef on failure, and $! will be set to the reason why the item couldn't be removed. that'll be ESRCH if the ITEM_ID was not found in the queue, or EPERM if the filter function returned false. so like, either she doesn't know her, or she knows her and said no. πŸ’€

remove_items FILTER_FUNCTION [, MAX_ITEM_COUNT ]

removes and returns items from the queue that match a FILTER_FUNCTION. remove_items() will return immediately if MAX_ITEM_COUNT items is specified and that many items have been removed from the queue. MAX_ITEM_COUNT is a bit of optimization for when the application already knows in advance how many items will match the FILTER_FUNCTION.

returns a list of items that were removed. each item is an array reference containing a priority, item ID, and payload. returns nothing if FILTER_FUNCTION matched nothing. (sad. empty-handed. πŸ₯Ί)

# Remove up to 12 monkeys.
my @monkeys = $queue->remove_items(\&monkeys, 12);
foreach my $monkey (@monkeys) {
  my ($priority, $id, $payload) = @$monkey;
  print(
    "Removed monkey:\n",
    "  priority = $priority\n",
    "  queue id = $id\n",
    "  payload  = $payload\n",
  );
}
⚠️ there is no guarantee which items will be removed if MAX_ITEM_COUNT is specified too low. so like, don't be surprised, bestie.

peek_items FILTER_FUNCTION [, MAX_ITEM_COUNT ]

peek_items() returns up to MAX_ITEM_COUNT items that match a given FILTER_FUNCTION without removing them from the queue. it's the "look but don't touch" of queue methods. window shopping. πŸ›οΈ

my @entire_queue = $queue->peek_items(sub { 1 });
foreach my $item (@entire_queue) {
  my ($priority, $id, $payload) = @$item;
  print(
    "Item:\n",
    "  priority = $priority\n",
    "  queue id = $id\n",
    "  payload  = $payload\n",
  );
}

adjust_priority ITEM_ID, FILTER_FUNCTION, DELTA

changes the priority of an item by DELTA. the item is identified by its ITEM_ID, and the change will only happen if the item's payload satisfies a FILTER_FUNCTION. returns the new priority, which is the previous priority + DELTA. and yes, DELTA may be negative. she contains multitudes. βž•βž–

my $new_priority = $queue->adjust_priority(
  $item_id, \&one_of_mine, 100
);

sub one_of_mine {
  my $payload = shift;
  return $payload->{owner} == $me;
}

returns undef if the item's priority could not be adjusted, and sets $! to explain why: ESRCH means the ITEM_ID could not be found, and EPERM means the FILTER_FUNCTION was not satisfied.

set_priority ITEM_ID, FILTER_FUNCTION, ABSOLUTE_PRIORITY

sets an item's priority to a new ABSOLUTE_PRIORITY. the item is identified by its ITEM_ID, and the change will only be allowed to happen if the item's payload satisfies a FILTER_FUNCTION. returns the new priority, which should match ABSOLUTE_PRIORITY. no more delta math, we're going straight to the number. πŸ’…

returns undef if the item's priority could not be set, and sets $! to explain why: ESRCH means the ITEM_ID could not be found, and EPERM means the FILTER_FUNCTION was not satisfied.

my $new_priority = $queue->set_priority(
  $item_id, \&one_of_mine, time() + 60
);

unless (defined $new_priority) {
  die "one of our submarines is missing: $item_id" if $! == ESRCH;
  die "set_priority disallowed for item $item_id" if $! == EPERM;
  die $!;
}

sub one_of_mine {
  $_[0]{owner} == $me;
}

πŸ” SEE ALSO πŸ”

  • POE
  • POE::Queue::Array

go read those next, bestie, they're the whole lore. πŸ“–βœ¨


πŸ› BUGS πŸ›

none known. she's flawless. periodt. πŸ’…βœ¨


πŸ‘‘ AUTHORS & COPYRIGHTS πŸ‘‘

please see POE for more information about authors, contributors, and POE's licensing. the receipts are all there, bestie, I'm not gonna retype them. 🫢


and that's the whole page, gorgeous!! this was POE::Queue(3pm) from perl v5.34.0, dated 2022-03-25, filed under User Contributed Perl Documentation. stay queued, stay prioritized, and remember: lower numbers go first. mwah!! πŸ’–πŸŽ€πŸ¦‹πŸŒΈ

πŸ“¦ where this page comes from