user/dev discussion of public-inbox itself
 help / color / mirror / code / Atom feed
blob 2ca0a63f33358922296234d582753ef89b37d3b0 2980 bytes (raw)
name: lib/PublicInbox/PlackLimiter.pm 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
 
# Copyright (C) all contributors <meta@public-inbox.org>
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
# generic Plack/PSGI middleware to expose PublicInbox::Limiter, (see __END__)
package PublicInbox::PlackLimiter;
use v5.12;
use parent qw(Plack::Middleware);
use PublicInbox::Limiter;

sub prepare_app { # called via Plack::Component (used by Plack::Middleware)
	my ($self) = @_;
	$self->{match_cb} //= sub { 1 };
	$self->{max} //= 2;
	$self->{run_queue} = [];
	$self->{running} = 0;
}

sub lim_fail { # limiter->may_start fail_cb
	my (undef, $code, $msg) = @_;
	[ $code, [ 'Content-Type' => 'text/plain',
		'Content-Length' => length($msg) ], [ $msg ] ]
}

sub stats ($) {
	my ($self) = @_;
	my $nq = scalar @{$self->{run_queue}};
	my $res = <<EOM;
running: $self->{running}
queued: $nq
max: $self->{max}
EOM
	[ 200, [ 'Content-Type' => 'text/plain',
		'Content-Length' => length($res) ], [ $res ] ]
}

sub app_call { # limiter->may_start start_cb
	my ($ctx, $self) = @_;
	my $wcb = delete $ctx->{psgi_wcb};
	my $env = delete $ctx->{env}; # avoid cyclic ref
	push @{$env->{'limiter.ctx'}}, $ctx; # handoff limiter.next.$self
	my $res = eval { $self->app->($env) };
	return warn("W: $@") if $@;
	ref($res) eq 'CODE' ? $res->($wcb) : $wcb->($res);
}

sub call {
	my ($self, $env) = @_;
	if (defined $self->{stats_match_cb}) {
		return stats $self if $self->{stats_match_cb}->($env);
	}
	return $self->app->($env) if !$self->{match_cb}->($env);
	sub { # capture write cb from PSGI server
		my $ctx = { env => $env, psgi_wcb => $_[0] };
		PublicInbox::Limiter::may_start(
				$self, \&app_call, $ctx, \&lim_fail);
	}
}

1;
__END__

=head1 NAME

PublicInbox::PlackLimiter - limit concurrency to parts of a PSGI app

=head1 SYNOPSIS

	# In your .psgi file
	use Plack::Builder;
	builder {

	# by default, only 2 requests may be processed at once:
	enable '+PublicInbox::PlackLimiter';

	# You will likely only want to limit certain expensive endpoints,
	# while allowing maximum concurrency for inexpensive endpoints.
	# You can do that by passing a `match_cb' parameter:
	enable '+PublicInbox::PlackLimiter',
		# some expensive endpoints for my public-inbox instance, YMMV
		match_cb => sub {
			my ($env) = @_;
			$env->{PATH_INFO} =~ m!/(?:[Ttd]/|.+\.
						(?:mbox\.gz|atom|html))\z!x ||
				$env->{QUERY_STRING} =~ /\bx=[tA]\b/
		},
		# You can increase `max' and `depth' to higher numbers
		max => 3, # maximum concurrent requests
		depth => 128, # maximum queue depth (size)
		# You can also enable a stats endpoint if you wish (optional):
		stats_match_cb => sub {
			my ($env) = @_;
			$env->{REQUEST_URI} eq '/stats' &&
				$env->{REMOTE_ADDR} eq '127.0.0.1'
		};
	# ...
	}; # /builder

=head1 DESCRIPTION

PublicInbox::PlackLimiter lets a sysadmin limit concurrency to certain
expensive endpoints while allowing the normal concurrency level of the
server to run inexpensive requests.

=head1 SEE ALSO

L<Plack> L<Plack::Builder> L<Plack::Middleware>

=cut

debug log:

solving 2ca0a63f ...
found 2ca0a63f in https://public-inbox.org/meta/20250321222231.3276910-3-e@80x24.org/
found a1cc51dc in https://80x24.org/public-inbox.git
preparing index
index prepared:
100644 a1cc51dcf49f7a178c7f4a986044aef021c1fd26	lib/PublicInbox/PlackLimiter.pm

applying [1/1] https://public-inbox.org/meta/20250321222231.3276910-3-e@80x24.org/
diff --git a/lib/PublicInbox/PlackLimiter.pm b/lib/PublicInbox/PlackLimiter.pm
index a1cc51dc..2ca0a63f 100644

Checking patch lib/PublicInbox/PlackLimiter.pm...
Applied patch lib/PublicInbox/PlackLimiter.pm cleanly.

index at:
100644 2ca0a63f33358922296234d582753ef89b37d3b0	lib/PublicInbox/PlackLimiter.pm

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://80x24.org/public-inbox.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).