-
Notifications
You must be signed in to change notification settings - Fork 374
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
Linux: TCP error 0x2714 after Apache reload #1242
Comments
Thank you @Snowknight26 for bringing this to our attention. We will investigate and get back to you on this. |
Hi @Snowknight26 I can't seem to be able to reproduce this issue. I've tried modifying the pdo script slightly and ran different tests. With connection pooling, I refreshed the browser while or after reloading Apache service. I did not see the aforementioned connection error but sometimes the SELECT query failed after the delay had elapsed. Could you please provide an ODBC trace and Apache error logs? Make sure you add
|
@yitam The failed SELECT query that you saw I believe is the exact issue, but without the error mode set to exceptions it wasn't possible to see the underlying reason (just "Call to a member function fetchAll() on boolean" or something like that). I'm still working on providing logs but I believe setting the PDO::ATTR_ERRMODE attribute to PDO::ERRMODE_EXCEPTION should let you reproduce it. |
@yitam odbc.log - ODBC trace |
Thanks @Snowknight26 I can reproduce it now, and I'll also examine the logs you provided. Will get back to you on this after some investigation. |
Hi @Snowknight26 I did try pdo_odbc and could also reproduce the same problem: |
When I was testing PDO_ODBC I believe FreeTDS was being used to provide the ODBC functionality. I was able to configure my environment to work with PDO_ODBC using Microsoft ODBC Driver 17 for SQL Server. To elaborate further, I changed the test script to this: <?php
$pdo = new PDO( 'sqlsrv:Server=server; LoginTimeout=15; MultipleActiveResultSets=false', '', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ] );
try {
var_dump( $pdo->query( 'SELECT 1 AS Row1' )->fetchAll( PDO::FETCH_ASSOC )[0]['Row1'] );
} catch( Throwable $e ) {
var_dump( $e );
}
try {
$pdo->query( "WAITFOR DELAY '00:00:05'" );
} catch( Throwable $e ) {
var_dump( $e );
}
try {
var_dump( $pdo->query( 'SELECT 2 AS Row1' )->fetchAll( PDO::FETCH_ASSOC )[0]['Row1'] );
} catch( Throwable $e ) {
var_dump( $e );
} Then I ran the same test with 5 different DSNs. Expand each section to see the output. sqlsrv:Server=server; LoginTimeout=15; MultipleActiveResultSets=false
odbc:driver={ODBC Driver 17 for SQL Server}; server=server; Trusted_Connection=yes
odbc:driver=FreeTDSODBC;servername=server
odbc:server
dblib:host=server
From the results, we can see the following:
It's puzzling that in the case of the first two DSNs, only the first query succeeds, as reloading Apache is a graceful operation and in theory should wait for the script to finish executing. My takeaway is that there's an issue with the Microsoft ODBC Driver 17 for SQL Server. Would that be a fair assessment to make? Do you know if the queries that failed due to TCP errors are fully executed on SQL Server's end, or is it impossible to tell? I'm not sure when the TCP connection is broken. |
Thanks @Snowknight26 for your detailed report and analysis. When re-reading your original description, you mentioned:
Can you elaborate further what exactly you meant by "once child processes are back"? In my case, the That is, so far I can only reproduce this if I run the |
My Apache instance is set up in is a high-traffic environment that receives millions of requests a day. As such, a cron job reloads Apache daily during off-peak hours to both prevent memory leaks and rotate logs. This is done using Per Apache's documentation, passing the graceful option causes Apache to gracefully restart.
With that in mind, what I meant by "once child processes are back" was the last sentence in that quote. Further Apache documentation states that 'graceful' does the following:
My interpretation is that the httpd daemon keeps running but child processes are stopped after requests have been processed and are started again. The reason for the WAITFOR in the test script is to more easily trigger the issue, simulating a typical load. With FreeTDS (either using PDO_ODBC or PDO_DBLIB), regardless of when the In contrast, with (I assume) Microsoft ODBC Driver 17 for SQL Server, when the In the case of my environment, with millions of hits per day, numerous PDOExceptions are thrown during the process of reloading the child processes/threads/workers (whatever they are called). |
Thanks again, @Snowknight26 |
I saw that bug report as I was submitting this one and considered they might be related - maybe something along the lines of the ODBC driver improperly handling SIGUSR1 or whatever other signal that's being sent. I did a little more testing to try to figure out what the behavior is on SQL Server's end when the error occurs. To start, I created a table and a stored procedure: USE [Contoso]
DROP TABLE IF EXISTS dbo.ConnectionTest
CREATE TABLE dbo.ConnectionTest
(
[Date] DATETIME2 NOT NULL,
Iteration INT NOT NULL,
Process VARCHAR(8) NOT NULL
)
GO
GRANT INSERT ON dbo.ConnectionTest TO [public]
GO
CREATE OR ALTER PROCEDURE dbo.ConnectionTestINS
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Iteration TINYINT = 0;
WHILE(@Iteration < 100)
BEGIN
SET @Iteration = @Iteration + 1;
INSERT INTO dbo.ConnectionTest([Date], Iteration, Process) VALUES (GETDATE(), @Iteration, 'Database');
WAITFOR DELAY '00:00:00.050';
END
SELECT @Iteration AS Iteration
END
GO
GRANT EXECUTE ON dbo.ConnectionTestINS TO [public]
GO Next, I modified the test script to the following: <?php
$pdo = new PDO( 'sqlsrv:Server=server; LoginTimeout=15; MultipleActiveResultSets=false', '', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ] );
$i = 0;
$process = 'Apache';
try {
$stmt = $pdo->prepare( 'INSERT INTO Contoso.dbo.ConnectionTest([Date], Iteration, Process) VALUES (GETDATE(), ?, ?)' );
$stmt->execute( [ $i++, $process ] );
}
catch( Throwable $e ) {}
try {
$stmt->execute( [ $i++, $process ] );
$pdo->query( 'EXEC Contoso.dbo.ConnectionTestINS' );
$stmt->execute( [ $i++, $process ] );
}
catch( Throwable $e ) {}
try {
$stmt->execute( [ $i++, $process ] );
}
catch( Throwable $e ) {} Next, I ran the test before, running What I found out is that it's possible for the error to occur during the stored procedure execution. Without implicit or explicit transactions, that means that instead of the SP inserting 100 rows, it's possible it inserts anywhere from 0 to 100. I'm sure you can see why this is alarming. Doing the same test with FreeTDS's ODBC showed that the SP always finished executing (thus inserting all 100 rows). I was hoping that the behavior would be the same regardless of which driver is used but that appears to not be the case. All this makes trying to make the permanent switch in my environment from FreeTDS to this driver a bit of a challenge. |
Hi @Snowknight26, thanks again for providing more details. After some investigation with the ODBC team, we conclude that this is actually by design. The process has been interrupted by a signal (from Apache), which breaks the connection and thus exceptions are thrown. On the other hand, FreeTDS does not handle the signal as it should have, which might get the process stuck in a state that might be problematic. In other words, signaling it may not get it unstuck. |
@yitam Based on Apache's graceful signal (emphasis mine)
it seems that the connection to the database should not exit immediately, but rather only after the current request, which happens anyway during the PDO object destruction. I'm just going to naively assume the SIGUSR1 signal is passed as follows: Under normal conditions, I assume the following happens when SIGUSR1 is sent.
If any currently executing PHP scripts are not immediately halted (basically having the equivalent of an 'exit' statement) in 4., why should the database driver immediately close the connection? I can see how the behavior would make sense in the case of SIGHUP/SIGTERM where the Apache daemon tries to immediately kill off all children, but not in the SIGUSR1 scenario. Unless I'm mistaken, there seems to be no way to safely wait and close the database connection via a signal? Better yet, is there a way to have the driver ignore SIGUSR1?
That's why timeouts not only in both FreeTDS and the ODBC driver are configurable, but also script execution time in PHP, no? Both of those mechanisms ensure that eventually (after their current request) Apache child processes restart. |
We see from strace that Apache does not set SA_RESTART when it sets up the SIGUSR1 handler. |
@v-chojas https://github.com/apache/httpd/blob/21f16155c38e406e0a0daaa60a539d66128cf044/server/mpm/prefork/prefork.c#L1065 prefork_run() calls ap_wait_or_timeout() (which I assume waits for child processes to finish), then calls ap_mpm_safe_kill() with AP_SIG_GRACEFUL (SIGUSR1), which passes the signal to kill(), with the PID of the child process. What do you mean by SA_RESTART? It seems odd that only Apache behaves this way. I haven't heard of issues with how its signal handling works. |
To answer your questions, @Snowknight26 , here are the snippets from strace:
As you can see, no SA_RESTART is used. Please see my reply to issue 885 to understand what this implies.
ODBC driver, upon getting SIGUSR1, breaks the connection and returns. FYI, you might want to read Proper way of handling EINTR in libraries , which explains why it may not be a good idea to "swallow" the signal. |
@yitam Hopefully I'll be able to determine what is calling sigaction() without the SA_RESTART flag instead of signal() (effectively the equivalent of sigaction() with the SA_RESTART flag) and potentially find a viable workaround. I have a hunch switching from the prefork to the event Apache MPM will do the trick but I'll follow up. |
I was able to confirm your findings about the signal handlers being setup without that flag.
Having said that though, I also dug deeper into the Apache Portable Runtime source code (the runtime that drives Apache) and found the following: https://svn.apache.org/repos/asf/apr/apr/trunk/threadproc/unix/signals.c /*
* Replace standard signal() with the more reliable sigaction equivalent
* from W. Richard Stevens' "Advanced Programming in the UNIX Environment"
* (the version that does not automatically restart system calls).
*/
APR_DECLARE(apr_sigfunc_t *) apr_signal(int signo, apr_sigfunc_t * func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#ifdef SA_INTERRUPT /* SunOS */
act.sa_flags |= SA_INTERRUPT;
#endif
#if defined(__osf__) && defined(__alpha)
/* XXX jeff thinks this should be enabled whenever SA_NOCLDWAIT is defined */
/* this is required on Tru64 to cause child processes to
* disappear gracefully - XPG4 compatible
*/
if ((signo == SIGCHLD) && (func == SIG_IGN)) {
act.sa_flags |= SA_NOCLDWAIT;
}
#endif
#if defined(__NetBSD__) || defined(DARWIN)
/* ignoring SIGCHLD or leaving the default disposition doesn't avoid zombies,
* and there is no SA_NOCLDWAIT flag, so catch the signal and reap status in
* the handler to avoid zombies
*/
if ((signo == SIGCHLD) && (func == SIG_IGN)) {
act.sa_handler = avoid_zombies;
}
#endif
if (sigaction(signo, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
} Note the last line of the comment:
Seems like this was intentional. All thread handling functionality in Apache uses apr_signal(), the function above - this means my hunch of switching MPMs would not work. I also checked nginx's source and found the following: ngx_int_t
ngx_init_signals(ngx_log_t *log)
{
ngx_signal_t *sig;
struct sigaction sa;
for (sig = signals; sig->signo != 0; sig++) {
ngx_memzero(&sa, sizeof(struct sigaction));
if (sig->handler) {
sa.sa_sigaction = sig->handler;
sa.sa_flags = SA_SIGINFO;
} else {
sa.sa_handler = SIG_IGN;
}
sigemptyset(&sa.sa_mask);
if (sigaction(sig->signo, &sa, NULL) == -1) {
#if (NGX_VALGRIND)
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"sigaction(%s) failed, ignored", sig->signame);
#else
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"sigaction(%s) failed", sig->signame);
return NGX_ERROR;
#endif
}
}
return NGX_OK;
} Based on the code, we also see that the SA_RESTART flag is not passed (only SA_SIGINFO). I was curious how other languages handled signals and stumbled upon a recent commit for Go (Golang): https://go-review.googlesource.com/c/go/+/232862/
It seems pretty clear that SA_RESTART isn't used frequently and shouldn't be relied on, even in modern code. So that brings us back to the original issue: at present, there is no way to advise the web server to exit safely while allowing the ODBC connection to remain open to finish the query. This holds true for both Apache and nginx which account for 70% of all web server usage. If PHP doesn't stop executing when SIGUSR1 is sent, it should be possible to configure either PDO_SQLSRV and/or the ODBC driver to not stop either. Is it possible to add a configuration option to prevent this behavior? |
|
Hi @Snowknight26, SA_RESTART is used to control whether the library function will resume, or it will return failure with error code EINTR. About what you have observed in the other scenarios, as @v-chojas mentioned, the applications want to use a signal to interrupt the current operation.
The configuration option is to use SA_RESTART, which indicates that you wish for the system to transparently do the retry without throwing the EINTR failure, as already explained in my reply and this message. As far as signal handling is concerned, this is by design. Microsoft ODBC driver is not going to change its behavior as the other applications that rely on this might be affected. |
Just to confirm we're all on the same page, since it's not possible to control the signaling behavior via PHP, one would have to control the behavior via Apache. As it currently stands, Apache (and nginx) is written such that signals are sent without the SA_RESTART flag, thus there being no way of controlling the ODBC driver's behavior, from a PHP developer's perspective. In the scenario outlined in this issue, the only way to change the ODBC driver's behavior is to modify Apache's (or nginx's) source code, potentially affecting every other module is loaded. Does that seem correct? |
@Snowknight26 yes, it's up to the applications what to do when using signals. That being said, you may want to discuss your question in Apache or nginx forums? |
@yitam This will be a long comment so bear with me. What do various database drivers do?ODBC Driver current behaviorWhen the ODBC driver receives an interrupt signal without SA_RESTART set, recvfrom(), the function used to retrieve data from a socket, returns ERESTARTSYS (To be restarted if SA_RESTART is set), which I believe is translated to EINTR in user code, and isn't restarted. With SA_RESTART, according to what you've said, recvfrom() would be restarted transparently. Why is this happening?That's how it was programmed. The driver is written to simply ignore EINTR and treat it as a normal error, exiting the socket read operation. Per the strace, the 4th parameter in recvfrom() is flags, but since it's set to 0, it is blocking.
Why is this an issue?The ODBC driver, and subsequently PDO_SQLSRV do not provide PHP with a mechanism of knowing how many bytes of data have been transferred during the query. You receive either all the data (success) or none (error). This will be important later. FreeTDSWhen FreeTDS receives an interrupt signal in poll(), the kernel automatically restarts the call (SA_RESTART is ignored, so poll() is always restarted with an updated timeout struct), and recvfrom() continues to receive data.
What does this mean?When FreeTDS receives the interrupt signal, it keeps retrieving data from the socket until either all the data has been transferred or the configurable timeout has been reached. Once again, you receive either all the data (success) or none (error). The database integrity is maintained. MySQL Connector/ODBC DriverI don't have a test environment set up so we'll just look at the source code. The connector uses the underlying MySQL server common network functions. https://github.com/mysql/mysql-server/blob/8.0/sql-common/net_serv.cc#L305 #ifndef MYSQL_SERVER
/*
In the client library, interrupted I/O operations are always retried.
Otherwise, it's either a timeout or an unrecoverable error.
*/
retry = vio_should_retry(net->vio);
#else
/*
In the server, interrupted I/O operations are retried up to a limit.
In this scenario, pthread_kill can be used to wake up
(interrupt) threads waiting for I/O.
*/
retry = vio_should_retry(net->vio) && ((*retry_count)++ < net->retry_count);
#endif What does this mean?When MySQL Connector/ODBC Driver receives the interrupt signal, it keeps retrieving data from the socket until either all the data has been transferred. Once again, you receive either all the data (success) or none (error). psqlODBC - PostgreSQL ODBC driverNo test environment again so source code it is. psqlODBC uses libpq for network connectivity. /* --------------------------------
* pq_recvbuf - load some bytes into the input buffer
*
* returns 0 if OK, EOF if trouble
* --------------------------------
*/
static int
pq_recvbuf(void)
{
/* removed for brevity */
/* Ensure that we're in blocking mode */
socket_set_nonblocking(false);
/* Can fill buffer from PqRecvLength and upwards */
for (;;)
{
int r;
r = secure_read(MyProcPort, PqRecvBuffer + PqRecvLength,
PQ_RECV_BUFFER_SIZE - PqRecvLength);
if (r < 0)
{
if (errno == EINTR)
continue; /* Ok if interrupted */
/*
* Careful: an ereport() that tries to write to the client would
* cause recursion to here, leading to stack overflow and core
* dump! This message must go *only* to the postmaster log.
*/
ereport(COMMERROR,
(errcode_for_socket_access(),
errmsg("could not receive data from client: %m")));
return EOF;
}
if (r == 0)
{
/*
* EOF detected. We used to write a log message here, but it's
* better to expect the ultimate caller to do that.
*/
return EOF;
}
/* r contains number of bytes read, so just incr length */
PqRecvLength += r;
return 0;
}
} What does this mean?psqlODBC keeps reading data when interrupted (continue statement in the for loop). Simba Teradata ODBC Driver with SQL ConnectorThis one isn't open source so let's check their documentation.
Default value is 1. What does this mean?Simba Teradata ODBC Driver retries the socket system calls, like every other driver so far (but the Microsoft one). The beauty of exposing a configuration option like this is that developers can choose what to do instead of being forced into an error state with no recovery like the Microsoft ODBC driver. What do non-database driver libraries do?PHPLet's check the source for PHP's socket wrappers that are accessible to userland code via fread, fwrite, etc. Writes: static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
{
php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
assert(data != NULL);
if (data->fd >= 0) {
/* snip */
ssize_t bytes_written = write(data->fd, buf, count);
/* snip */
if (bytes_written < 0) {
if (PHP_IS_TRANSIENT_ERROR(errno)) {
return 0;
}
if (errno == EINTR) {
/* TODO: Should this be treated as a proper error or not? */
return bytes_written;
}
/* snip */
}
return bytes_written;
} else {
/* snip */
return (ssize_t) fwrite(buf, 1, count, data->file);
}
} Reads: static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count)
{
php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
ssize_t ret;
assert(data != NULL);
if (data->fd >= 0) {
/* snip */
ret = read(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count));
if (ret == (size_t)-1 && errno == EINTR) {
/* Read was interrupted, retry once,
If read still fails, give up with feof==0
so script can retry if desired */
ret = read(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count));
}
/* snip */
ret = fread(buf, 1, count, data->file);
stream->eof = feof(data->file);
}
return ret;
} What does this mean?Write operations return the number of bytes written on EINTR. Since you know the length of what you're try to write and you just received the number of bytes actually written, you can try again, as one would expect. When I said "This will be important later." under the Microsoft ODBC driver section, this is what I was referring to. PHP gives the developer the control and mechanism to retry socket operations. The Microsoft ODBC driver does not expose any way for a developer to control the socket operations. It's fine that it doesn't, but because the developer expects 'all or nothing', the query should either fail instantly or succeed fully. Not somewhere inbetween. libwww-perlhttps://rt.cpan.org/Public/Bug/Display.html?id=32356
The patch goes on to show code that applies a looping mechanism in the case that EINTR is returned. What does this mean?Their sysread/can_read functions automatically retry when interrupted. This is a network library so it makes sense. HaskellTo the source code we go.
else if e == eINTR
-- On EINTR it is good practice to just retry.
then retry
closeFdWith
-- The c_close operation may (according to Posix documentation) fails with EINTR or EBADF or EIO.
-- EBADF: Should be ruled out by the library's design.
-- EINTR: It is best practice to just retry the operation what we do here.
-- EIO: Only occurs when filesystem is involved (?).
-- Conclusion: Our close should never fail. If it does, something is horribly wrong.
( const $ fix $ \retry-> do
i <- c_close fd
if i < 0 then do
e <- getErrno
if e == eINTR
then retry
else throwIO (SocketException e)
else return ()
) fd
-- | Receive a message on a socket and additionally yield the peer address.
--
-- - Calling `recvFrom` on a `close`d socket throws @EBADF@ even if the former file descriptor has been reassigned.
-- - The operation takes a buffer size in bytes a first parameter which
-- limits the maximum length of the returned `Data.ByteString.ByteString`.
-- - @EAGAIN@, @EWOULDBLOCK@ and @EINTR@ and handled internally and won't be thrown. What does this mean?Again, operations are retried. What is the common consensus for handling EINTR/SA_RESTART?This depends heavily on the operating system implementation but the general censuses is that you can't rely on SA_RESTART. http://linux-kernel.2935.n7.nabble.com/strace-accept-ERESTARTSYS-and-EINTR-td227496.html
https://beej.us/guide/bgnet/html/
https://stackoverflow.com/a/51220021/1695130
(emphasis mine) https://www2.eecs.berkeley.edu/Pubs/TechRpts/1999/CSD-99-1056.pdf
(emphasis mine) https://en.wikipedia.org/wiki/Unix_philosophy
(emphasis mine)
(emphasis mine) https://www.linuxprogrammingblog.com/all-about-linux-signals?page=show
(emphasis mine) https://kirste.userpage.fu-berlin.de/chemnet/use/info/libc/libc_21.html
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568149
(emphasis mine) Fenner, B., Stevens, W. R., Rudoff, A. M. (2004). UNIX Network Programming: The sockets networking API. Spain: Addison-Wesley.
(emphasis mine) https://cis.temple.edu/~ingargio/cis307/readings/signals.html
http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
https://programmer.help/blogs/linux-interrupted-system-calls.html
(emphasis mine) https://perldoc.perl.org/perlipc#Restartable-system-calls
(emphasis mine) https://news.ycombinator.com/item?id=5089014
(emphasis mine) https://blog.reverberate.org/2011/04/eintr-and-pc-loser-ing-is-better-case.html
(emphasis mine) https://lwn.net/Articles/414618/
(the irony, considering this is .NET) And a follow-up:
(emphasis mine) https://softwareengineering.stackexchange.com/a/409060
It's not possible for the user (aka PHP developer using PDO_SQLSRV) to resume. https://bugzilla.redhat.com/show_bug.cgi?id=1427500
(emphasis mine) https://people.gnome.org/~federico/blog/rust-libstd-syscalls-and-errors.html
(emphasis mine) https://lists.x.org/archives/xorg-devel/2011-April/021242.html
(Signed off by developer at nvidia.com) https://golang.org/doc/go1.14#runtime
(emphasis mine)
https://www.winehq.org/pipermail/wine-devel/2014-February/103112.html
(emphasis mine) Linux kernel automatically retrying in various libraries: ConclusionI think it's pretty fair to say that code should generally retry interrupted operations regardless of what signal handler flags are set, solely because those can't be relied on. Because a non-transactional database query can be interrupted with the current ODBC driver, the database can be left in an invalid state, with no ability to recover from PHP's end. PHP has no control over Apache or any system it is running on as far as signals go (disregarding CLI), so it only makes sense to at minimum give the developer control over the driver's behavior. A perfect example of this is the Simba Teradata ODBC Driver. They expose a flag that userland code can change, giving developers more control over connections, ensuring database integrity. At minimum, the Microsoft ODBC driver should do the same. At best, it should always retry system calls. As quoted above, your colleagues working on the .NET framework for Linux would agree. |
@Snowknight26 Thanks for the extensive research on this. I'm taking a bit of a deeper dive into signal handling in *nix and seeing if this behavior is something we want to change in the ODBC driver or at least make configurable. I can't make any promises, but from what I've read so far, it does seem like something we should seriously consider for the ODBC driver. Regards, |
PHP Driver version or file name
5.9.0
SQL Server version
Microsoft SQL Server 2019 (RTM-CU8-GDR) (KB4583459) - 15.0.4083.2 (X64)
Client operating system
RHEL 7.8
Kernel: Linux 3.10.0-1127.18.2.el7.x86_64 x86_64
PHP version
7.4.16 NTS (7.4.16-1.el7.remi)
Microsoft ODBC Driver version
Microsoft ODBC Driver for SQL Server 17.7.1.1 (msodbcsql17.x86_64 17.7.1.1-1)
unixODBC 2.3.7 (unixODBC.x86_64 2.3.7-1.rh)
Table schema
N/A
Problem description
Reloading Apache during a long-running query causes the next query to be executed after child processes come back to generate the following error:
SQLSTATE[08S01]: [Microsoft][ODBC Driver 17 for SQL Server]TCP Provider: Error code 0x2714
Occasionally this error occurs instead:
SQLSTATE[08S01]: [Microsoft][ODBC Driver 17 for SQL Server]Communication link failure
Expected behavior and actual behavior
The next query should succeed.
Repro code or steps to reproduce
Create a script with the following:
Save the script in a location that can be served up by Apache (httpd24-httpd.x86_64 2.4.34-18.el7 in my case) and executed by PHP.
For example: http://localhost/test.php
Continually refresh the page after after it has been served (will take a minimum of 5 seconds due to the WAITFOR).
Reload the Apache service. My RHEL installation required
/bin/systemctl reload httpd24-httpd.service
.Once child processes are back, reloading the page (executing the script) will generate the error stated above.
The issue only seems to happen when using PDO_SQLSRV and not any other PDO driver.
For example, I've tried the following and was unable to reproduce:
new PDO( 'odbc:server', '', '' )
- using PDO_ODBC insteadnew PDO( 'dblib:server', '', '' )
- using FreeTDSTried the following but the issue still occurred:
/bin/systemctl restart httpd24-httpd.service
)odbcinst -j
output:Relevant section of odbcinst.ini:
Let me know if anything else is needed.
The text was updated successfully, but these errors were encountered: