Bug 41920: Limit number of concurrent reports that can be run simultaneously for an instance of Koha

As a continuation of bug 41919,  we should limit the total number of simultaneous reports that can be run at a time.

Test Plan:
1) Set total_running_reports_per_instance_limit to 2 in your koha-conf.xml
2) Create 3 long running reports like: SELECT COUNT(*) FROM items i1 JOIN items i2 JOIN items i3 JOIN items i4 JOIN items i5 JOIN items i6
3) Run each report in turn by opening a new tab, browsing to the reports
   module, and choosing to run that report
5) Note the first two continue to run, but the last one gives an error
   that you have too many reports running simultaneously!

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Kyle M Hall 2026-02-25 13:05:03 -05:00 committed by Pedro Amorim
parent 92cbbd59d5
commit 7ba7bbab7d
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
5 changed files with 69 additions and 1 deletions

View File

@ -103,7 +103,8 @@ sub running {
$report_ids{$1} = 1 if $row->{info} =~ /saved_sql\.id:\s*(\d+)/; $report_ids{$1} = 1 if $row->{info} =~ /saved_sql\.id:\s*(\d+)/;
} }
return $class->search( { id => { -in => [ keys %report_ids ] } } ); my $rs = $class->search( { id => { -in => [ keys %report_ids ] } } );
return wantarray ? $rs->as_list : $rs;
} }
=head3 _processlist_rows =head3 _processlist_rows

View File

@ -348,6 +348,7 @@ __END_SRU_PUBLICSERVER__
<!-- SQL report limits, 0 means the feature is disabled --> <!-- SQL report limits, 0 means the feature is disabled -->
<duplicate_running_reports_per_user_limit>0</duplicate_running_reports_per_user_limit> <duplicate_running_reports_per_user_limit>0</duplicate_running_reports_per_user_limit>
<total_running_reports_per_user_limit>0</total_running_reports_per_user_limit> <total_running_reports_per_user_limit>0</total_running_reports_per_user_limit>
<total_running_reports_per_instance_limit>0</total_running_reports_per_instance_limit>
<!-- Configuration for X-Forwarded-For --> <!-- Configuration for X-Forwarded-For -->
<!-- Set enable_trusted_proxy_for_headers to 0 to never trust proxy headers, always use given REMOTE_ADDRESS --> <!-- Set enable_trusted_proxy_for_headers to 0 to never trust proxy headers, always use given REMOTE_ADDRESS -->

View File

@ -165,6 +165,7 @@
<!-- SQL report limits, 0 means the feature is disabled --> <!-- SQL report limits, 0 means the feature is disabled -->
<duplicate_running_reports_per_user_limit>0</duplicate_running_reports_per_user_limit> <duplicate_running_reports_per_user_limit>0</duplicate_running_reports_per_user_limit>
<total_running_reports_per_user_limit>0</total_running_reports_per_user_limit> <total_running_reports_per_user_limit>0</total_running_reports_per_user_limit>
<total_running_reports_per_instance_limit>0</total_running_reports_per_instance_limit>
<!-- Configuration for X-Forwarded-For --> <!-- Configuration for X-Forwarded-For -->
<!-- Set enable_trusted_proxy_for_headers to 0 to never trust proxy headers, always use given REMOTE_ADDRESS --> <!-- Set enable_trusted_proxy_for_headers to 0 to never trust proxy headers, always use given REMOTE_ADDRESS -->

View File

@ -1714,6 +1714,8 @@
<p>This report is already running. Please wait for it to finish before running it again.</p> <p>This report is already running. Please wait for it to finish before running it again.</p>
[% ELSIF ( error.total_running_reports_limit_exceeded ) %] [% ELSIF ( error.total_running_reports_limit_exceeded ) %]
<p>You have too many reports running at the moment. Please wait for some of them to finish before running this report again.</p> <p>You have too many reports running at the moment. Please wait for some of them to finish before running this report again.</p>
[% ELSIF ( error.total_running_reports_per_instance_limit_exceeded ) %]
<p>There are too many total reports running at the moment. Please wait for some of them to finish before running this report again.</p>
[% ELSIF ( error.sqlerr ) %] [% ELSIF ( error.sqlerr ) %]
<p>This report contains an SQL keyword: <strong>[% error.sqlerr | html %]</strong>. </p> <p>This report contains an SQL keyword: <strong>[% error.sqlerr | html %]</strong>. </p>
<p>Use of this keyword is not allowed in Koha reports due to security and data integrity risks. Only SELECT queries are allowed.</p> <p>Use of this keyword is not allowed in Koha reports due to security and data integrity risks. Only SELECT queries are allowed.</p>

View File

@ -885,6 +885,42 @@ if ( $op eq 'run' ) {
my $template_code = $input->param('template_code'); my $template_code = $input->param('template_code');
my $want_full_chart = $input->param('want_full_chart') || 0; my $want_full_chart = $input->param('want_full_chart') || 0;
# Determine if the user is running this report too many times
my @duplicate_running_report_ids;
my $duplicate_running_reports_per_user_limit = C4::Context->config('duplicate_running_reports_per_user_limit');
if ( $duplicate_running_reports_per_user_limit && C4::Context->userenv ) {
my $user_id = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
@duplicate_running_report_ids = Koha::Reports->running( { report_id => $report_id, user_id => $user_id } );
}
my $duplicate_running_reports_per_user_limit_exceeded =
$duplicate_running_reports_per_user_limit
? scalar(@duplicate_running_report_ids) >= $duplicate_running_reports_per_user_limit
: 0;
# Determine if the user is running too many reports in total
my @total_running_for_user_report_ids;
my $total_running_reports_per_user_limit = C4::Context->config('total_running_reports_per_user_limit');
if ( $total_running_reports_per_user_limit && C4::Context->userenv ) {
my $user_id = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
@total_running_for_user_report_ids = Koha::Reports->running( { user_id => $user_id } );
}
my $total_running_reports_per_user_limit_exceeded =
$total_running_reports_per_user_limit
? scalar(@total_running_for_user_report_ids) >= $total_running_reports_per_user_limit
: 0;
# Determine if the Koha instance is running too many reports in total
my @total_running_for_instance_report_ids;
my $total_running_reports_per_instance_limit = C4::Context->config('total_running_reports_per_instance_limit');
if ( $total_running_reports_per_instance_limit && C4::Context->userenv ) {
my $instance_id = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
@total_running_for_instance_report_ids = Koha::Reports->running();
}
my $total_running_reports_per_instance_limit_exceeded =
$total_running_reports_per_instance_limit
? scalar(@total_running_for_instance_report_ids) >= $total_running_reports_per_instance_limit
: 0;
# offset algorithm # offset algorithm
if ( $input->param('page') ) { if ( $input->param('page') ) {
$offset = ( $input->param('page') - 1 ) * $limit; $offset = ( $input->param('page') - 1 ) * $limit;
@ -1070,6 +1106,33 @@ if ( $op eq 'run' ) {
'id' => $report_id, 'id' => $report_id,
'template_code' => $template_code, 'template_code' => $template_code,
); );
} elsif ( $duplicate_running_reports_per_user_limit_exceeded
|| $total_running_reports_per_user_limit_exceeded
|| $total_running_reports_per_instance_limit_exceeded )
{
$template->param(
'sql' => $sql,
'original_sql' => $original_sql,
'id' => $report_id,
'execute' => 1,
'name' => $name,
'notes' => $notes,
'errors' => [
$duplicate_running_reports_per_user_limit_exceeded
? { duplicate_running_report_ids => \@duplicate_running_report_ids }
: (),
$total_running_reports_per_user_limit_exceeded
? { total_running_reports_per_user_limit_exceeded => \@total_running_for_user_report_ids }
: (),
$total_running_reports_per_instance_limit_exceeded
? {
total_running_reports_per_instance_limit_exceeded => \@total_running_for_instance_report_ids
}
: (),
],
'sql_params' => \@sql_params,
'param_names' => \@param_names,
);
} else { } else {
my ( $sql, $header_types ); my ( $sql, $header_types );
eval { ( $sql, $header_types ) = $report->prep_report( \@param_names, \@sql_params ); }; eval { ( $sql, $header_types ) = $report->prep_report( \@param_names, \@sql_params ); };