Connection Pools and resource management in Swoole

Introduction

In this post I will explain the idea of resource pool and how it should be used. A resource pool is a limited pool from which a resource is obtained, used and then released back to the pool for other threads to use it. Usually these pools are actually connection pools so from now on I will use this term. In an a separate post it will be show how a Connection Pool is implemented in Swoole.

Because of the persistent nature of Swoole this allows for resource pools to be used. Unlike the traditional PHP setup which tears down all the context/variables at the end of each request, in Swoole the heap variables (static and global ones) survive between the individual requests which allows for a PHP resource to be reused. This is to say that a database connection opened in request 1 can be used in request 2, 3, 4…

For the sake of simplicity in this post I will be looking at the case when the connections in the pool are established at the time the pool is initialized. Another option would be the connections to be established at the first time they are needed and there is no free connection (up to the pool size).

Obtaining resources

In the very simple case a single thread (in Swoole context – coroutine) requests a connection from the pool, executes some queries and releases this connection back to the pool. The more complicated case is when there is a nested function call which also needs a connection of the same type. In this case requesting again a connection should not result in obtaining a second connection from the pool but return the already obtained one from the parent scope and increment a reference counter. Respectively when the child frame releases the obtained connection this should not actually release it but decrement the reference counter. The connection should be released only when the refcount reaches 0.

When a new coroutine is created it should never share connections with any other coroutine and the first time a connection is attempted to be obtained the pool should always return a new one no matter if there is already a connection obtained in the parent coroutine. The child coroutine has its own stack and within that stack the same already obtained connection needs to be returned by the pool when a connection is requested.

The image below illustrates the principle. It shows three coroutines (one master and two subcoroutines) each with their stacks.

The second graph shows the number of busy connections at any given moment. The graph also shows a case of improper release of a resource. The light slate blue connection should be released immediately after the single transaction in this scope is executed. Instead (as shown on the graph) its release is delayed until the end of the scope which is incorrect. On the second graph the red line represents the timely release of this connection (immediately after the query ). A proper resource deallocation is shown in the first stack frame of the first coroutine with the red connection – it is released immediately after the last query and long before the end of the scope.

Releasing resources

Releasing resources is a bit more complex than the obtaining because of two reasons:

  • developers just forget to do it on time
  • it must be ensured that the resource is released even in case of unexpected scope exit (exception, improperly placed return statement).

For the purpose of resource management Swoole provides the defer() mechanism. The callback provided to Coroutine::defer() will be executed at the end of the coroutine execution (please see Coroutines in Swoole).

<?php
$ConnectionPool = new ConnectionPool();
go(function() use ($ConnectionPool) {
    $Connection = $ConnectionPool->get_connection();
    Swoole\Coroutine::defer(function() use ($Connection) {
        $Connection->free();
    });
    //function body
});

While defer() guarantees the resource will be deallocated it may not be done in a timely manner. If we take the graph above as an example the light slate blue connection in the second stack frame of the third coroutine would be released at a much later stage at the coroutine end instead of the scope end. To illustrate this consider this code:

<?php

function some_func($ConnectionPool) {
    $Connection = $ConnectionPool->get_connection();
    Swoole\Coroutine::defer(function() use ($Connection) {
        $Connection->free();
    });
    //query 1
    //query 2
}

$ConnectionPool = new ConnectionPool();
go(function() use ($ConnectionPool) {
    some_func($ConnectionPool);
    //do some more time consuming stuff
    //prolonging the execution of the coroutine
    //thus delaying the release of the resource by defer()
});

To guarantee that the allocated connections are released Resource acquisition is initialization (RAII) and more specifically Scope-based Resource Management (SBRM) should be used. In SBRM automatic variables are used to control the ref counters – increment it when the resource is requested/object created and decrement it when the object is destroyed by going out of scope (be it because of return statement or due to an exception thrown). In a separate post I will show the implementation of this. Here I will just give an example:

<?php

function some_func($ConnectionPool) {
    $Connection = $ConnectionPool->get_connection($ScopeReference);
    //query 1
    //query 2
    //some other time consuming stuff
    //at the scope end $ScopeReference is destroyed thus decrementing the refcount for $Connection
    //since the connection is not obtained in the parent scope as well it will be released here
}

$ConnectionPool = new ConnectionPool();
go(function() use ($ConnectionPool) {
    some_func($ConnectionPool);
    //do some more time consuming stuff
    //prolonging the execution of the coroutine
    //but the connection is already released
});

Which is better than using defer() as the resource is freed at the end of the scope instead of the end of the coroutine. But is not the ideal scenario – the best will be to have an explicit release after the resource is no longer needed (query2). The SBRM should stay in place to handle the case. So the correct code is:

<?php

function some_func($ConnectionPool) {
    $Connection = $ConnectionPool->get_connection($ScopeReference);
    //query 1
    //query 2
    $Connection->free();//release as soon as possible
    //some other time consuming stuff
    //$ScopeReference will trigger resource release only in case an exception is throw somewhere between get_connection() and free()
}

$ConnectionPool = new ConnectionPool();
go(function() use ($ConnectionPool) {
    some_func($ConnectionPool);
    //do some more time consuming stuff
    //prolonging the execution of the coroutine
    //but the connection is already released
});

Additional reading

Find me at

Veselin Kenashkov

Developer at Azonmedia
Loves PHP, frameworks and old computers. Currently interested in Swoole and Vuejs.
Veselin Kenashkov
Find me at

Latest posts by Veselin Kenashkov (see all)

Share if you liked it

Leave a Reply

Your email address will not be published. Required fields are marked *