Skip to content

Commit b05a2fd

Browse files
committed
Refuse closing a MySQL connection another fiber is suspended in
mysqli_close(), mysqli_stmt_close() and a reconnect would free the connection under the parked call; they throw while its stream is busy.
1 parent 0ee980f commit b05a2fd

5 files changed

Lines changed: 69 additions & 0 deletions

File tree

‎ext/mysqli/mysqli_api.c‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ PHP_FUNCTION(mysqli_close)
275275

276276
MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_INITIALIZED);
277277

278+
/* Another fiber is suspended in a call on it, which the close would free */
279+
if (mysql->mysql && mysqlnd_conn_is_busy(mysql->mysql->data)) {
280+
zend_throw_error(NULL, "Concurrent access to a MySQL connection");
281+
RETURN_THROWS();
282+
}
283+
278284
php_mysqli_close(mysql, MYSQLI_CLOSE_EXPLICIT, ((MYSQLI_RESOURCE *)(Z_MYSQLI_P(mysql_link))->ptr)->status);
279285
((MYSQLI_RESOURCE *)(Z_MYSQLI_P(mysql_link))->ptr)->status = MYSQLI_STATUS_UNKNOWN;
280286

@@ -1470,6 +1476,11 @@ PHP_FUNCTION(mysqli_stmt_close)
14701476
}
14711477
MYSQLI_FETCH_RESOURCE_STMT(stmt, mysql_stmt, MYSQLI_STATUS_VALID);
14721478

1479+
if (stmt->stmt->data && mysqlnd_conn_is_busy(stmt->stmt->data->conn)) {
1480+
zend_throw_error(NULL, "Concurrent access to a MySQL connection");
1481+
RETURN_THROWS();
1482+
}
1483+
14731484
mysqli_stmt_close(stmt->stmt, false);
14741485
stmt->stmt = NULL;
14751486
php_clear_stmt_bind(stmt);

‎ext/mysqli/mysqli_nonapi.c‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, bool is_real_connect, b
140140
if (mysql->mysql && mysqli_resource &&
141141
(mysqli_resource->status > MYSQLI_STATUS_INITIALIZED))
142142
{
143+
if (mysqlnd_conn_is_busy(mysql->mysql->data)) {
144+
zend_throw_error(NULL, "Concurrent access to a MySQL connection");
145+
RETURN_THROWS();
146+
}
143147
/* already connected, we should close the connection */
144148
php_mysqli_close(mysql, MYSQLI_CLOSE_IMPLICIT, mysqli_resource->status);
145149
/* the link is not connected anymore, even if the new connection fails */
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
IO hooks: closing a connection another fiber is suspended in throws instead of freeing it
3+
--EXTENSIONS--
4+
mysqli
5+
--SKIPIF--
6+
<?php
7+
require_once 'skipifconnectfailure.inc';
8+
?>
9+
--FILE--
10+
<?php
11+
require_once 'connect.inc';
12+
include __DIR__ . '/../../standard/tests/streams/hooks/scheduler.inc';
13+
14+
$db = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
15+
$stmt = $db->prepare('SELECT 1');
16+
$scheduler = new Scheduler();
17+
Io\Hooks\set_hooks($scheduler);
18+
19+
$scheduler->spawn(function () use ($db) {
20+
echo "A: query\n";
21+
$r = $db->query('SELECT SLEEP(0.5) AS s');
22+
echo "A: ", $r->fetch_assoc()['s'], "\n";
23+
});
24+
$scheduler->spawn(function () use ($db, $stmt) {
25+
foreach ([fn () => $db->close(), fn () => $stmt->close(), fn () => $db->real_connect()] as $close) {
26+
try {
27+
$close();
28+
} catch (Error $e) {
29+
echo "B: ", $e->getMessage(), "\n";
30+
}
31+
}
32+
});
33+
$scheduler->loop();
34+
Io\Hooks\set_hooks(null);
35+
var_dump($db->close());
36+
?>
37+
--EXPECT--
38+
A: query
39+
B: Concurrent access to a MySQL connection
40+
B: Concurrent access to a MySQL connection
41+
B: Concurrent access to a MySQL connection
42+
A: 0
43+
bool(true)

‎ext/mysqlnd/mysqlnd.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ PHPAPI MYSQLND * mysqlnd_connection_connect(MYSQLND * conn,
9797

9898
PHPAPI void mysqlnd_debug(const char *mode);
9999

100+
/* An operation of another flow is on the connection's stream */
101+
PHPAPI bool mysqlnd_conn_is_busy(const MYSQLND_CONN_DATA *conn);
102+
100103
/* Query */
101104
#define mysqlnd_fetch_into(result, flags, ret_val) (result)->m.fetch_into((result), (flags), (ret_val) ZEND_FILE_LINE_CC)
102105
#define mysqlnd_fetch_row_c(result) (result)->m.fetch_row_c((result))

‎ext/mysqlnd/mysqlnd_connection.c‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "mysqlnd_debug.h"
2828
#include "mysqlnd_ext_plugin.h"
2929
#include "zend_smart_str.h"
30+
#include "main/php_io_hooks.h"
3031

3132

3233
extern MYSQLND_CHARSET *mysqlnd_charsets;
@@ -2337,3 +2338,10 @@ mysqlnd_connection_init(const size_t client_flags, const bool persistent, MYSQLN
23372338
DBG_RETURN(ret);
23382339
}
23392340
/* }}} */
2341+
2342+
PHPAPI bool mysqlnd_conn_is_busy(const MYSQLND_CONN_DATA *conn)
2343+
{
2344+
MYSQLND_VIO *vio = conn ? conn->vio : NULL;
2345+
php_stream *stream = vio ? vio->data->m.get_stream(vio) : NULL;
2346+
return stream && php_io_stream_busy(stream);
2347+
}

0 commit comments

Comments
 (0)