php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80587 PDO driver instance shared between different PDO objects
Submitted: 2021-01-04 15:47 UTC Modified: 2021-01-11 14:38 UTC
From: jeremys at ha dot com Assigned:
Status: Open Package: PDO related
PHP Version: 7.3.25 OS: RHEL 7.8
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2021-01-04 15:47 UTC] jeremys at ha dot com
Description:
------------
When instantiating a new PDO object with a specific driver (PDO_DBLIB in this example), the driver settings that were used from the first PDO object are maintained across further PDO objects, post-garbage collection.

In my specific example, I'm using FreeTDS with a query timeout of 300 seconds ("timeout = 300" in freetds.conf). Theoretically, each new PDO instance using dblib as the driver should default to the 300 second query timeout unless specified otherwise, such as via the $options parameter in PDO::__construct().

In the test script, the first PDO object is created with a connection timeout of 5 seconds, overriding FreeTDS's configured 300 seconds. After releasing the first object, a 2nd PDO object is created without specifying any driver options ($options parameter in PDO::__construct() is null), meaning the default of 300 seconds should be used. Instead, the value of the first PDO object using dblib is used.

This applies to all options, but the timeout is the easiest example to test.
This prevents effectively using custom driver options.

Note the different hostnames in the test script.

Appears to happen in PHP 8 as well based on the PDO source code.

Test script:
---------------
<?php

// 5 second timeout overrides freetds.conf
$pdo = new PDO( 'dblib:dbname=northwinds;host=contoso', 'username', 'password', [ PDO::ATTR_TIMEOUT => 5 ] );
try
{
    $pdo->query( "WAITFOR DELAY '00:00:10'" );
    var_dump( $pdo->query( "SELECT 'output' AS Output" )->fetch()['Output'] );
}
catch( Throwable $e )
{
    var_dump( stripos( $pdo->errorInfo()[2], 'DBPROCESS is dead or not enabled' ) === 0 );
}
$pdo = null;

// no $options defined, timeout should be 300 seconds; hostname is different
$pdo = new PDO( 'dblib:dbname=northwinds;host=contoso2', 'username', 'password' );
try
{
    $pdo->query( "WAITFOR DELAY '00:00:10'" );
    var_dump( $pdo->query( "SELECT 'output' AS Output" )->fetch()['Output'] );
}
catch( Throwable $e )
{
    var_dump( stripos( $pdo->errorInfo()[2], 'DBPROCESS is dead or not enabled' ) === 0 );
}

Expected result:
----------------
bool(true)
string(6) "output"

Actual result:
--------------
bool(true)
bool(true)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-01-10 21:52 UTC] adambaratz@php.net
-Status: Open +Status: Feedback
 [2021-01-10 21:52 UTC] adambaratz@php.net
I left a comment on the timeout issue on #80586. Just to make sure I'm following the more general problem you're describing, could you give an example that uses a different option and describe the behavior you'd expect to see?

One thing to point out with the timeout options is that no matter how pdo_dblib calls dbsetlogintime or dbsettime, the timeouts will apply to all PDO instances. Note that those functions don't take any kind of driver handle, just a timeout value.
 [2021-01-11 14:38 UTC] jeremys at ha dot com
-Status: Feedback +Status: Open
 [2021-01-11 14:38 UTC] jeremys at ha dot com
I have freetds.conf set with the following options:

timeout = 300
connect timeout = 15

This is done for 2 reasons. First, it allows our userland code to implement a retry mechanism in case a connection to the database fails within that 15 second threshold. Second, because our code frequently calls queries that vary greatly in execution time, the 300 second timeout prevents longer queries from timing out.

#80586 (and to a lesser extent #80546) is closely related to this issue in that, when trying to pass other driver options to the PDO constructor, such as PDO::ATTR_ERRMODE, doing so inherently overrides both timeout values to 30 seconds.

This becomes an issue as our code has several situations where an individual script can connect to multiple database servers, either concurrently or sequentially, with different driver options.

Let's say that the first connection creates a new PDO instance and PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION in the constructor (I know, I can choose not to do it in the constructor but rather as a separate setAttribute() call after the fact). The timeout for this first PDO instance for database server A is silently overridden to 30 seconds for both timeout types. Next, a 2nd PDO instance is created for database server B where we explicitly want a short query timeout of 10 seconds. Passing PDO::ATTR_TIMEOUT to the constructor of the 2nd PDO instance causes the timeout to be set to 10 seconds for both PDO instances.

This applies to all options. In the above example, I could have set PDO::DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER to true in the first PDO instance but false in the 2nd, yet reading a UNIQUEIDEITNFIER column would come back as a string from the 2nd instance.

Let me know if you need more info.
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Fri Mar 05 19:01:24 2021 UTC