|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2006-07-04 05:27 UTC] msquillace at sogei dot it
Description:
------------
In both PHP 5.1.4 with oci8.c v1.299 (2006/05/18 13:20:00) and the current OCI8 extension from php5.2-200607031230.tar.bz2 the active persistent connection misbehaves if the Apache process times out in the middle of a DB operation.
Specifically, if a (first) PHP script run by the Apache process N and (re)using a persistent connection is executing a very long running DB operation or is waiting on a lock when Apache times out, it doesn't send any output to the client (expected). Note that PHP itself doesn't timeout in this situation (expected).
Now, if a new (second) PHP script is fed to the same Apache process and tries to reuse the same persistent connection it will get warnings similar to the following:
Warning: oci_execute(): ORA-24909: call in progress. Current operation cancelled in /php/prova_oracle.php on line 15
Warning: ocifetchinto(): ORA-24374: define not done before fetch or execute and fetch in /php/prova_oracle.php on line 17
A third PHP script will get slightly different warnings:
Warning: oci_execute(): ORA-24909: call in progress. Current operation cancelled in /php/prova_oracle.php on line 15
Warning: ocifetchinto(): ORA-24338: statement handle not executed in /php/prova_oracle.php on line 17
What happens next (fourth PHP script on the same Apache process) is quite bad and differs in the two above mentioned PHP configurations:
PHP 5.1.4 -> the Apache process segfaults;
current PHP snapshot -> the Apache process freezes FOREVER, ignoring any Apache timeout.
In order to solve the problem I believe PHP_RSHUTDOWN_FUNCTION(oci) should be made aware of PHP state when Apache times out (first script), and destroy the persistent connection(s) used by the timed-out script.
To make a long debugging session brief, I found a couple of ways to determine if an OCI function was executing at the time of an Apache timeout; the one requiring least code modifications is implemented in the following patch to function php_oci_persistent_helper() in oci8.c:
diff -u old/oci8.c new/oci8.c
--- old/oci8.c 2006-06-28 18:30:51.000000000 +0200
+++ new/oci8.c 2006-07-03 16:17:03.000000000 +0200
@@ -1724,6 +1724,7 @@
{
time_t timestamp;
php_oci_connection *connection;
+ char *function;
timestamp = time(NULL);
@@ -1731,6 +1732,11 @@
connection = (php_oci_connection *)le->ptr;
if (connection->used_this_request) {
+ function = get_active_function_name(TSRMLS_C);
+ if(function && (2 < strlen(function)) && (!strncasecmp(function, "oci", 3))) {
+ return 1; /* OCI call still in progress, close all (used) persistent connection(s). */
+ }
+
if (connection->descriptors) {
zend_hash_destroy(connection->descriptors);
efree(connection->descriptors);
The above should work as long as the current naming convention holds; it is based on the (empirical) observation that get_active_function_name() normally returns NULL in a module 's RSHUTDOWN function, but returns the interrupted PHP function name after an Apache timeout.
Reproduce code:
---------------
First of all, start Apache in single process mode (I'd also set to, like, 30s the default timeout in httpd.conf):
[root@websrv tmp]# /path/to/httpd/httpd -X
To reproduce the problem, we can lock a DB record using e.g. sqlplus without committing:
SQL> select campo1 from table1 for update;
In a second terminal session via e.g. Curl, or from a browser we then run the following script once:
<?php
$pid=getmypid();
echo "pid:$pid<br>\n";
$conn = oci_pconnect("user", "pwd", "test");
$query = 'select campo1 from table1 for update';
$stid = OCIParse($conn, $query);
oci_execute($stid, OCI_DEFAULT);
while($succ = OCIFetchInto($stid, $row)) {
foreach($row as $item) {
echo $item." ";
}
echo "<br>\n";
}
?>
The script will timeout and return nothing (expected).
We then run the following script three times (any other script reusing the same persistent connection would do), resulting in the sequence of events described above:
<?php
$pid=getmypid();
echo "pid:$pid<br>\n";
$conn = oci_pconnect("user", "pwd", "test");
$query = "select table_name from user_tables where table_name='TABLE1'";
$stid = OCIParse($conn, $query);
oci_execute($stid, OCI_DEFAULT);
while($succ = OCIFetchInto($stid, $row)) {
foreach($row as $item) {
echo $item." ";
}
echo "<br>\n";
}
?>
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Mon Oct 27 18:00:01 2025 UTC |
Ok, I tested with with some different code: <?php $c = oci_pconnect(); for (;;) {} ?> In this case it DOES time out and PG(connection_status) has the correct value. But, for example, with $query = "begin dbms_lock.sleep(5); end;"; it doesn't timeout, waiting for the statement to finish executing, so PG(connection_status) is 0. In the same time (because the statement was successfully executed) the connection can be reused again. So the only thing I can say is "cannot reproduce".