The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

# Copyright 2018 - present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
use Test::More 0.96;
use Test::Deep qw/!blessed/;
use UUID::URandom qw/create_uuid/;
use boolean;
use utf8;
use MongoDB;
use lib "t/lib";
use MongoDBTest qw/
build_client
skip_unless_mongod
skip_unless_sessions
get_test_db
server_version
server_type
clear_testdbs
get_unique_collection
uuid_to_string
/;
skip_unless_mongod();
skip_unless_sessions();
my @events;
sub clear_events { @events = () }
sub event_count { scalar @events }
sub event_cb { push @events, $_[0] }
my $conn = build_client(
monitoring_callback => \&event_cb,
);
my $testdb = get_test_db($conn);
my $server_version = server_version($conn);
my $server_type = server_type($conn);
my $coll = $testdb->get_collection('test_collection');
subtest 'endSession closes sessions on server' => sub {
my $session_count = 10;
my @sessions;
# for checking later that they've all been culled
my %session_ids;
# create all the sessions early so we end up with different ID's for each
# of them instead of re-using the same session ID for multiple from the
# pool
for ( 0 .. $session_count - 1 ) {
my $session = $conn->start_session;
$session_ids{ uuid_to_string( $session->_server_session->session_id->{id}->data ) } = 1;
push @sessions, $session;
}
for my $i ( 0 .. $session_count - 1 ) {
$coll->insert_one( { '_id' => $i + 1 }, { session => $sessions[$i] } );
}
$_->end_session for @sessions;
my $s_count = count_sessions_in_hash (
[ map { $_->session_id } @{ $conn->_server_session_pool->_server_session_pool } ],
\%session_ids,
);
is $s_count, $session_count, 'All sessions in pool';
# called in destruction of client normally
$conn->_server_session_pool->end_all_sessions;
my $response = $events[-1];
is $response->{reply}->{ok}, 1, 'Got ok 1 from ending all sessions';
};
subtest 'expiry of old sessions on retire' => sub {
my $session_count = 10;
my @sessions;
my %session_ids;
for ( 0 .. $session_count - 1 ) {
my $session = $conn->start_session;
$session_ids{ uuid_to_string( $session->_server_session->session_id->{id}->data ) } = 1;
push @sessions, $session;
}
is scalar( keys %session_ids ), $session_count, 'got enough unique sessions';
for my $i ( 0 .. $session_count - 1 ) {
# force last used to actually be set
$sessions[$i]->_server_session->update_last_use;
$sessions[$i]->end_session;
}
my $before_retire_count = count_sessions_in_hash (
[ map { $_->session_id } @{ $conn->_server_session_pool->_server_session_pool } ],
\%session_ids,
);
is $before_retire_count, $session_count, 'All sessions in pool';
my @to_reorganise;
# find all sessions to modify
for my $i ( 0 .. $#{ $conn->_server_session_pool->_server_session_pool } ) {
my $uuid = uuid_to_string( $conn->_server_session_pool->_server_session_pool->[$i]->session_id->{id}->data );
if ( $session_ids{ $uuid } ) {
push @to_reorganise, $i;
}
}
# reverse sort array, so that we move the furthest in the list first
@to_reorganise = sort { $b <=> $a } @to_reorganise;
# modify and move all known sessions from highest index to lowest
for my $i ( @to_reorganise ) {
my $move_sess = splice @{ $conn->_server_session_pool->_server_session_pool }, $i, 1;
$move_sess->_set_last_use( $move_sess->last_use - ( 40 * 60 ) );
# send this session to the end of the array
push @{ $conn->_server_session_pool->_server_session_pool }, $move_sess;
}
my $new_session = $conn->start_session;
# this should trigger a retiring of sessions from the back of the pool
$new_session->end_session;
my $after_retire_count = count_sessions_in_hash (
[ map { $_->session_id } @{ $conn->_server_session_pool->_server_session_pool } ],
\%session_ids,
);
is $after_retire_count, 0, 'All sessions retired from pool';
};
sub count_sessions_in_hash {
my ( $sessions, $session_ids ) = @_;
my $s_count = 0;
for my $session ( @$sessions ) {
my $s_uuid = uuid_to_string ( $session->{id}->data );
if ( exists $session_ids->{ $s_uuid } ) {
$s_count++;
}
}
return $s_count;
}
clear_testdbs;
done_testing;