1
# Copyright (C) 2008-2009 Sun Microsystems, Inc. All rights reserved.
2
# Use is subject to license terms.
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; version 2 of the License.
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
# General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
18
package GenTest::Reporter::Deadlock;
21
@ISA = qw(GenTest::Reporter);
25
use GenTest::Constants;
27
use GenTest::Reporter;
28
use GenTest::Executor::MySQL;
34
use constant PROCESSLIST_PROCESS_TIME => 5;
35
use constant PROCESSLIST_PROCESS_INFO => 7;
37
# The time, in seconds, we will wait for a connect before we declare the server hanged
38
use constant CONNECT_TIMEOUT_THRESHOLD => 20;
40
# Minimum lifetime of a query before it is considered suspicios
41
use constant QUERY_LIFETIME_THRESHOLD => 600; # Seconds
43
# Number of suspicious queries required before a deadlock is declared
44
use constant STALLED_QUERY_COUNT_THRESHOLD => 5;
46
# Number of times the actual test duration is allowed to exceed the desired one
47
use constant ACTUAL_TEST_DURATION_MULTIPLIER => 2;
52
my $actual_test_duration = time() - $reporter->testStart();
54
if ($actual_test_duration > ACTUAL_TEST_DURATION_MULTIPLIER * $reporter->testDuration()) {
55
say("Actual test duration ($actual_test_duration seconds) is more than ".(ACTUAL_TEST_DURATION_MULTIPLIER)." times the desired duration (".$reporter->testDuration()." seconds)");
56
return STATUS_SERVER_DEADLOCKED;
60
return $reporter->monitor_threaded();
62
return $reporter->monitor_nonthreaded();
66
sub monitor_nonthreaded {
68
my $dsn = $reporter->dsn();
70
# We connect on every run in order to be able to use the mysql_connect_timeout to detect very debilitating deadlocks.
74
# We directly call exit() in the handler because attempting to catch and handle the signal in a more civilized
75
# manner does not work for some reason -- the read() call from the server gets restarted instead
77
sigaction SIGALRM, new POSIX::SigAction sub {
78
exit (STATUS_SERVER_DEADLOCKED);
79
} or die "Error setting SIGALRM handler: $!\n";
81
my $prev_alarm1 = alarm (CONNECT_TIMEOUT_THRESHOLD);
82
$dbh = DBI->connect($dsn, undef, undef, { mysql_connect_timeout => CONNECT_TIMEOUT_THRESHOLD * 2} );
84
if (defined GenTest::Executor::MySQL::errorType($DBI::err)) {
86
return GenTest::Executor::MySQL::errorType($DBI::err);
87
} elsif (not defined $dbh) {
89
return STATUS_UNKNOWN_ERROR;
92
my $processlist = $dbh->selectall_arrayref("SHOW FULL PROCESSLIST");
95
my $stalled_queries = 0;
97
foreach my $process (@$processlist) {
99
($process->[PROCESSLIST_PROCESS_INFO] ne '') &&
100
($process->[PROCESSLIST_PROCESS_TIME] > QUERY_LIFETIME_THRESHOLD)
103
# say("Stalled query: ".$process->[PROCESSLIST_PROCESS_INFO]);
107
if ($stalled_queries >= STALLED_QUERY_COUNT_THRESHOLD) {
108
say("$stalled_queries stalled queries detected, declaring deadlock at DSN $dsn.");
110
foreach my $status_query (
112
"SHOW ENGINE INNODB STATUS"
113
# "SHOW OPEN TABLES" - disabled due to bug #46433
115
say("Executing $status_query:");
116
my $status_result = $dbh->selectall_arrayref($status_query);
117
print Dumper $status_result;
120
return STATUS_SERVER_DEADLOCKED;
126
sub monitor_threaded {
127
my $reporter = shift;
132
# We create two threads:
133
# * alarm_thread keeps a timeout so that we do not hang forever
134
# * dbh_thread attempts to connect to the database and thus can hang forever because
135
# there are no network-level timeouts in DBD::mysql
138
my $alarm_thread = threads->create( \&alarm_thread );
139
my $dbh_thread = threads->create ( \&dbh_thread, $reporter );
143
# We repeatedly check if either thread has terminated, and if so, reap its exit status
146
foreach my $thread ($alarm_thread, $dbh_thread) {
147
$status = $thread->join() if defined $thread && $thread->is_joinable();
149
last if defined $status;
153
# And then we kill the remaining thread.
155
foreach my $thread ($alarm_thread, $dbh_thread) {
156
next if !$thread->is_running();
157
# Windows hangs when joining killed threads
159
$thread->kill('SIGKILL');
161
$thread->kill('SIGKILL')->join();
169
local $SIG{KILL} = sub { threads->exit() };
171
# We sleep in small increments so that signals can get delivered in the meantime
173
foreach my $i (1..CONNECT_TIMEOUT_THRESHOLD) {
177
say("Entire-server deadlock detected.");
178
return(STATUS_SERVER_DEADLOCKED);
182
local $SIG{KILL} = sub { threads->exit() };
183
my $reporter = shift;
184
my $dsn = $reporter->dsn();
186
# We connect on every run in order to be able to use a timeout to detect very debilitating deadlocks.
188
my $dbh = DBI->connect($dsn, undef, undef, { mysql_connect_timeout => CONNECT_TIMEOUT_THRESHOLD * 2, PrintError => 1, RaiseError => 0 });
190
if (defined GenTest::Executor::MySQL::errorType($DBI::err)) {
191
return GenTest::Executor::MySQL::errorType($DBI::err);
192
} elsif (not defined $dbh) {
193
return STATUS_UNKNOWN_ERROR;
196
my $processlist = $dbh->selectall_arrayref("SHOW FULL PROCESSLIST");
197
return GenTest::Executor::MySQL::errorType($DBI::err) if not defined $processlist;
199
my $stalled_queries = 0;
201
foreach my $process (@$processlist) {
203
($process->[PROCESSLIST_PROCESS_INFO] ne '') &&
204
($process->[PROCESSLIST_PROCESS_TIME] > QUERY_LIFETIME_THRESHOLD)
210
if ($stalled_queries >= STALLED_QUERY_COUNT_THRESHOLD) {
211
say("$stalled_queries stalled queries detected, declaring deadlock at DSN $dsn.");
212
print Dumper $processlist;
213
return STATUS_SERVER_DEADLOCKED;
221
my $reporter = shift;
222
my $server_pid = $reporter->serverInfo('pid');
223
my $datadir = $reporter->serverVariable('datadir');
226
($^O eq 'MSWin32') ||
229
my $cdb_command = "cdb -p $server_pid -c \".dump /m $datadir\mysqld.dmp;q\"";
230
say("Executing $cdb_command");
231
system($cdb_command);
233
say("Killing mysqld with pid $server_pid with SIGHUP in order to force debug output.");
234
kill(1, $server_pid);
237
say("Killing mysqld with pid $server_pid with SIGSEGV in order to capture core.");
238
kill(11, $server_pid);
246
return REPORTER_TYPE_PERIODIC | REPORTER_TYPE_DEADLOCK;