Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Hyperf\DbConnection\Db blocks other coroutines from getting a database pool connection #6611

Open
kingIZZZY opened this issue Mar 21, 2024 · 6 comments
Labels
bug Something isn't working
Milestone

Comments

@kingIZZZY
Copy link
Contributor

Linux <redacted> 5.4.0-172-generic #190-Ubuntu SMP Fri Feb 2 23:24:22 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
PHP 8.3.3-1+ubuntu20.04.1+deb.sury.org+1 (cli) (built: Feb 15 2024 18:38:21) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.3-1+ubuntu20.04.1+deb.sury.org+1, Copyright (c), by Zend Technologies
friendsofhyperf/tinker             v3.1.7                                   The Powerful REPL for Hyperf.
hyperf/cache                       v3.1.10                                  A cache component for hyperf.
hyperf/code-parser                 v3.1.4                                   A code parser component for Hyperf.
hyperf/codec                       v3.1.10                                  A codec component for Hyperf.
hyperf/collection                  v3.1.7                                   Hyperf Collection package which come from illuminate/collections
hyperf/command                     v3.1.7                                   Command for hyperf
hyperf/conditionable               v3.1.0                                   Hyperf Macroable package which come from illuminate/conditionable
hyperf/config                      v3.1.2                                   An independent component that provides configuration container.
hyperf/context                     v3.1.0                                   A coroutine/application context library.
hyperf/contract                    v3.1.2                                   The contracts of Hyperf.
hyperf/coordinator                 v3.1.7                                   Hyperf Coordinator
hyperf/coroutine                   v3.1.1                                   Hyperf Coroutine
hyperf/database                    v3.1.10                                  A flexible database library.
hyperf/db-connection               v3.1.7                                   A hyperf db connection handler for hyperf/database.
hyperf/devtool                     v3.1.5                                   A Devtool for Hyperf.
hyperf/di                          v3.1.6                                   A DI for Hyperf.
hyperf/dispatcher                  v3.1.0                                   A HTTP Server for Hyperf.
hyperf/engine                      v2.10.4                                  Coroutine engine provided by swoole.
hyperf/engine-contract             v1.9.1                                   Contract for Coroutine Engine
hyperf/event                       v3.1.0                                   an event manager that implements PSR-14.
hyperf/exception-handler           v3.1.5                                   Exception handler for hyperf
hyperf/framework                   v3.1.0                                   A coroutine framework that focuses on hyperspeed and flexible, specifically...
hyperf/guzzle                      v3.1.0                                   Swoole coroutine handler for guzzle
hyperf/helper                      v3.1.0                                   A function helper package that could help developer solved the problem quic...
hyperf/http-message                v3.1.0                                   microservice framework base on swoole
hyperf/http-server                 v3.1.6                                   A HTTP Server for Hyperf.
hyperf/logger                      v3.1.9                                   A logger component for hyperf.
hyperf/macroable                   v3.1.0                                   Hyperf Macroable package which come from illuminate/macroable
hyperf/memory                      v3.1.0                                   An independent component that use to operate and manage memory.
hyperf/model-listener              v3.1.10                                  A model listener for Hyperf.
hyperf/pipeline                    v3.1.0                                   Hyperf Macroable package which come from illuminate/pipeline
hyperf/polyfill-coroutine          v3.1.0                                   This component provides features added short functions.
hyperf/pool                        v3.1.0                                   An independent universal connection pool component.
hyperf/process                     v3.1.0                                   A process component for hyperf.
hyperf/serializer                  v3.1.0                                   A serializer component for Hyperf.
hyperf/server                      v3.1.9                                   A base server library for Hyperf.
hyperf/stdlib                      v3.1.0                                   A stdlib component for Hyperf.
hyperf/stringable                  v3.1.0                                   Hyperf Stringable package which come from illuminate/support
hyperf/support                     v3.1.10                                  A support component for Hyperf.
hyperf/tappable                    v3.1.0                                   Hyperf Macroable package which come from illuminate/tappable
hyperf/testing                     v3.1.3                                   Testing for hyperf
hyperf/translation                 v3.1.0                                   An independent translation component, forked by illuminate/translation.
hyperf/utils                       v3.1.0                                   A tools package that could help developer solved the problem quickly.

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 5.1.1
Built => Dec  1 2023 13:40:51
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 1.1.1f  31 Mar 2020
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
pcre => enabled
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
mysqlnd => enabled
async_redis => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_fiber_mock => Off => Off
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608

Issue:

Hyperf\DbConnection\Db occupies pool connections the entire lifetime of the coroutine, blocking other coroutines from getting a DB connection.

For example if a coroutine performs work such as: 1) query the database 2) do some HTTP requests to APIs etc. 3) update the database or other tasks etc. then Hyperf\DbConnection\Db will get a pool connection in step 1 and keep it reserved and does not release it back to the pool even if step 2 HTTP/API requests take time, other coroutines waiting to use Db:: cannot get from the pool until this coroutine ends (when defer() is called to release the DB connection back to the pool)

See hyperf/db-connection/src/ConnectionResolver.php where $pool->get() and defer() and $connection->release(); is called

Steps To Reproduce:

Create many coroutines to perform database tasks using Db:: and other tasks which take some time like HTTP requests etc.
Early / first coroutines will get database pool connections right away and proceed with their tasks
But later coroutines will wait in the connection pool channel until those db connections are released, even if the Early coroutines take 10 or 15 or 30sec+ to perform their HTTP tasks, later coroutines cannot even perform their 1st database task until the early coroutines finish and return.

Solution?

Possibility to release the pool connection on-demand earlier in the coroutine before performing time consuming work like HTTP requests or other such tasks?

Db::select(...); // gets a db pool connection fast
...
Db::release(); // releases the DB connection early
...
// time-consuming work - does not block other coroutines from using DB!
...
Db::update(...); // gets another pool connection fast because everyone is releasing before doing long work
...
// coroutine ends - ConnectionResolver.php `defer()` will do the last final releasing the DB connection
@kingIZZZY kingIZZZY added the bug Something isn't working label Mar 21, 2024
@limingxinleo
Copy link
Member

You can use wait() function to solve this.

use function Hyperf\Coroutine\wait;

$result = wait(fn() => Db::select(...));

$result = wait(fn() => Db::update(...));

@lazychanger
Copy link
Contributor

你的运行环境不在Coroutine环境,通过Swoolerun或者别的方式从Coroutine环境启动就可以了。

@kingIZZZY
Copy link
Contributor Author

kingIZZZY commented Mar 22, 2024

@limingxinleo wow thanks brilliant idea I never thought about performing each database task inside a coroutine 👍👌 I was not aware of that wait() function (I guess it's not documented yet)
Would it still be beneficial to have a Db::release() feature so no need to call wait() which creates an additional channel and additional coroutine for each database operation?...

@lazychanger yes it is running in coroutine environment, Constant::OPTION_ENABLE_COROUTINE => true, (see above swoole output swoole.enable_coroutine)

@limingxinleo
Copy link
Member

This feature may be added in v3.2

@limingxinleo limingxinleo added this to the v3.2 milestone Mar 22, 2024
@kingIZZZY
Copy link
Contributor Author

kingIZZZY commented Mar 22, 2024

Amazing!
If I had enough knowledge I would even try contributing this code myself, I'm just not sure where does this new Db::release() method belong

  • hyperf/database/src/Connection.php ?
  • hyperf/db-connection/src/Connection.php ?
  • hyperf/db-connection/src/ConnectionResolver.php ?
  • Other?

I guess it just needs to get the $connection from the coroutine context and call ->release() on it similar to ConnectionResolver.php connection() when it calls defer()

@kingIZZZY
Copy link
Contributor Author

kingIZZZY commented Mar 22, 2024

There is another problem though - if we call this new Db::release() method in the current coroutine and there are no other coroutines trying to use Db:: then this coroutine will yield/suspend and waste time waiting (push() on the connection pool channel) until a new coroutine requests a pool connection (to pop() the channel)

I tried to solve this:

// 1. Get the connection object out of the coroutine context
$connectionName = 'default';
$contextKey = "database.connection.$connectionName"; // see ConnectionResolver.php getContextKey()
$db = \Hyperf\Context\Context::get($contextKey);

// 2. Send ANOTHER coroutine to release that connection, so our coroutine can continue running
\Hyperf\Engine\Coroutine::create(fn($db) => $db->release(), $db);

// Now our coroutine can continue executing and not suspend / wait for connection pool channel!

// 3. In our coroutine delete / set null the $db variable
// this is to satisfy ConnectionResolver.php connection() condition if(Context::has($id))
$db = null;
unset($db);
\Hyperf\Context\Context::set($contextKey, null);
\Hyperf\Context\Context::destroy($contextKey);

// If we call Db:: again in our coroutine it should successfully obtain another connection from the pool
// ... right?

But then I start getting this error - maybe I'm doing something wrong and there is a solution to do it right?

Fatal error: Uncaught Swoole\Error: Socket#23 has already been bound to another coroutine#38, reading of the same socket in coroutine#39 at the same time is not allowed in vendor/hyperf/database/src/Connection.php:269
Stack trace:
#0 vendor/hyperf/database/src/Connection.php(269): PDO->prepare('SELECT y.*, if(...')
#1 vendor/hyperf/database/src/Connection.php(1136): Hyperf\Database\Connection->Hyperf\Database\{closure}('SELECT y.*, if(...', Array)
#2 vendor/hyperf/database/src/Connection.php(1102): Hyperf\Database\Connection->runQueryCallback('SELECT y.*, if(...', Array, Object(Closure))
#3 vendor/hyperf/database/src/Connection.php(260): Hyperf\Database\Connection->run('SELECT y.*, if(...', Array, Object(Closure))
#4 vendor/hyperf/db-connection/src/Connection.php(48): Hyperf\Database\Connection->select('SELECT y.*, if(...', Array)
#5 vendor/hyperf/db-connection/src/Traits/DbConnection.php(41): Hyperf\DbConnection\Connection->__call('select', Array)
#6 vendor/hyperf/db-connection/src/Db.php(66): Hyperf\DbConnection\Connection->select('SELECT y.*, if(...', Array)
#7 app/Tasks/SomethingTask.php(1800): Hyperf\DbConnection\Db::__callStatic('select', Array)
#8 [internal function]: App\Tasks\SomethingTask::batch('1', Object(Swoole\Coroutine\WaitGroup), Array, 2, false)
#9 {main}
  thrown in vendor/hyperf/database/src/Connection.php on line 269
[2024-03-01 21:57:15 #64661.0]	WARNING	Server::check_worker_exit_status(): worker(pid=64663, id=1) abnormal exit, status=255, signal=0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants